Pushkar02-n commited on
Commit
9206499
·
verified ·
1 Parent(s): f2361c4

Delete ui

Browse files
ui/.env.example DELETED
File without changes
ui/Readme.md DELETED
@@ -1,103 +0,0 @@
1
- # AniFind — Next.js Frontend
2
-
3
- AI-powered anime recommendation frontend. Communicates with the FastAPI backend on Hugging Face Spaces.
4
-
5
- ---
6
-
7
- ## Project Structure
8
-
9
- ```
10
- anifind/
11
- ├── app/
12
- │ ├── api/
13
- │ │ └── recommend/
14
- │ │ └── route.ts ← Server-side proxy (API key never reaches browser)
15
- │ ├── globals.css ← Global styles + fonts
16
- │ ├── layout.tsx ← Root HTML layout + metadata
17
- │ └── page.tsx ← Main UI (search, filters, results)
18
- ├── lib/
19
- │ └── types.ts ← TypeScript types shared across app
20
- ├── .env.example ← Copy to .env.local with real values
21
- ├── .gitignore ← Includes .env.local — never commit secrets!
22
- ├── next.config.mjs
23
- ├── tailwind.config.ts
24
- ├── tsconfig.json
25
- └── package.json
26
- ```
27
-
28
- ---
29
-
30
- ## Local Development
31
-
32
- ### 1. Install dependencies
33
- ```bash
34
- npm install
35
- ```
36
-
37
- ### 2. Set up environment variables
38
- ```bash
39
- cp .env.example .env.local
40
- # Edit .env.local with your actual values:
41
- # API_URL=https://your-username-your-space.hf.space
42
- # API_KEY=your_secret_key
43
- ```
44
-
45
- ### 3. Run dev server
46
- ```bash
47
- npm run dev
48
- # Open http://localhost:3000
49
- ```
50
-
51
- ---
52
-
53
- ## Deploy to Vercel
54
-
55
- ### Step 1 — Push to GitHub
56
- ```bash
57
- git init
58
- git add .
59
- git commit -m "initial commit"
60
- git remote add origin https://github.com/your-username/anifind.git
61
- git push -u origin main
62
- ```
63
-
64
- ### Step 2 — Connect to Vercel
65
- 1. Go to https://vercel.com → "Add New Project"
66
- 2. Import your GitHub repo
67
- 3. Vercel auto-detects Next.js — no build config needed
68
-
69
- ### Step 3 — Add Environment Variables in Vercel
70
- In your Vercel project → Settings → Environment Variables, add:
71
-
72
- | Name | Value |
73
- |-----------|------------------------------------------------|
74
- | `API_URL` | `https://your-username-your-space.hf.space` |
75
- | `API_KEY` | `your_secret_key` |
76
-
77
- ⚠️ Do NOT prefix with NEXT_PUBLIC_ — these must stay server-side only.
78
-
79
- ### Step 4 — Deploy
80
- Click Deploy. Every future `git push` to `main` auto-deploys.
81
-
82
- ---
83
-
84
- ## How the API key is protected
85
-
86
- ```
87
- Browser → /api/recommend (Next.js server route) → HF Spaces FastAPI
88
-
89
- API_KEY lives here (Vercel env var, server-side only)
90
- Browser never sees it
91
- ```
92
-
93
- The browser only ever calls `/api/recommend` on your own domain.
94
- The actual HF Spaces URL and API key are injected server-side by Vercel.
95
-
96
- ---
97
-
98
- ## Environment Variables Reference
99
-
100
- | Variable | Where set | Description |
101
- |-----------|------------------|--------------------------------|
102
- | `API_URL` | Vercel + .env.local | HF Spaces FastAPI base URL |
103
- | `API_KEY` | Vercel + .env.local | Shared secret with FastAPI |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/app/api/recommend/route.ts DELETED
@@ -1,142 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
-
3
- // ─── Config ───────────────────────────────────────────────────────────────────
4
- const DAILY_LIMIT = 10;
5
- const COOLDOWN_HRS = 2;
6
- const COOLDOWN_MS = COOLDOWN_HRS * 60 * 60 * 1000;
7
-
8
- const REDIS_COUNT_KEY = "anifind:global_count";
9
- const REDIS_RESET_KEY = "anifind:reset_at";
10
-
11
- // ─── Tiny Upstash REST helper (no SDK needed) ─────────────────────────────────
12
- async function redis(command: string[]): Promise<unknown> {
13
- const url = process.env.UPSTASH_REDIS_REST_URL;
14
- const token = process.env.UPSTASH_REDIS_REST_TOKEN;
15
-
16
- if (!url || !token) throw new Error("Missing Upstash env vars");
17
-
18
- const res = await fetch(`${url}/${command.map(encodeURIComponent).join("/")}`, {
19
- headers: { Authorization: `Bearer ${token}` },
20
- });
21
-
22
- const json = await res.json() as { result: unknown };
23
- return json.result;
24
- }
25
-
26
- // ─── GET — frontend polls this to show live counter ───────────────────────────
27
- export async function GET() {
28
- try {
29
- const [count, resetAt] = await Promise.all([
30
- redis(["GET", REDIS_COUNT_KEY]),
31
- redis(["GET", REDIS_RESET_KEY]),
32
- ]);
33
-
34
- const currentCount = parseInt(String(count ?? "0"));
35
- const resetTime = resetAt ? parseInt(String(resetAt)) : null;
36
- const now = Date.now();
37
- const isLocked = currentCount >= DAILY_LIMIT && resetTime !== null && now < resetTime;
38
- const remainingMs = isLocked && resetTime ? resetTime - now : 0;
39
-
40
- return NextResponse.json({
41
- count: isLocked ? DAILY_LIMIT : currentCount,
42
- limit: DAILY_LIMIT,
43
- isLocked,
44
- remainingMs,
45
- remainingHrs: Math.ceil(remainingMs / (1000 * 60 * 60)),
46
- });
47
- } catch {
48
- return NextResponse.json({ count: 0, limit: DAILY_LIMIT, isLocked: false, remainingMs: 0, remainingHrs: 0 });
49
- }
50
- }
51
-
52
- // ─── POST — actual search proxy ───────────────────────────────────────────────
53
- export async function POST(req: NextRequest) {
54
- try {
55
- const apiUrl = process.env.API_URL;
56
- const apiKey = process.env.API_KEY;
57
-
58
- if (!apiUrl || !apiKey) {
59
- return NextResponse.json(
60
- { error: "Server misconfiguration: missing API_URL or API_KEY" },
61
- { status: 500 }
62
- );
63
- }
64
-
65
- // ── Rate limit check ────────────────────────────────────────────────────
66
- try {
67
- const [count, resetAt] = await Promise.all([
68
- redis(["GET", REDIS_COUNT_KEY]),
69
- redis(["GET", REDIS_RESET_KEY]),
70
- ]);
71
-
72
- const currentCount = parseInt(String(count ?? "0"));
73
- const resetTime = resetAt ? parseInt(String(resetAt)) : null;
74
- const now = Date.now();
75
-
76
- if (currentCount >= DAILY_LIMIT) {
77
- if (resetTime && now < resetTime) {
78
- const remainingMs = resetTime - now;
79
- const remainingHrs = Math.ceil(remainingMs / (1000 * 60 * 60));
80
- return NextResponse.json(
81
- {
82
- error: `Global search limit reached (${DAILY_LIMIT} searches). Resets in ${remainingHrs} hr${remainingHrs !== 1 ? "s" : ""}.`,
83
- isLocked: true,
84
- remainingHrs,
85
- remainingMs,
86
- },
87
- { status: 429 }
88
- );
89
- } else {
90
- await Promise.all([
91
- redis(["SET", REDIS_COUNT_KEY, "0"]),
92
- redis(["DEL", REDIS_RESET_KEY]),
93
- ]);
94
- }
95
- }
96
- } catch (redisErr) {
97
- console.warn("Redis rate limit check failed, allowing request:", redisErr);
98
- }
99
-
100
- // ── Forward to FastAPI ──────────────────────────────────────────────────
101
- const body = await req.json();
102
-
103
- const response = await fetch(`${apiUrl}/recommend`, {
104
- method: "POST",
105
- headers: {
106
- "Content-Type": "application/json",
107
- "X-API-Key": apiKey,
108
- },
109
- body: JSON.stringify(body),
110
- });
111
-
112
- if (!response.ok) {
113
- const err = await response.text();
114
- return NextResponse.json(
115
- { error: `Backend error: ${err}` },
116
- { status: response.status }
117
- );
118
- }
119
-
120
- const data = await response.json();
121
-
122
- // ── Increment counter AFTER successful response ─────────────────────────
123
- try {
124
- const newCount = await redis(["INCR", REDIS_COUNT_KEY]) as number;
125
- if (newCount >= DAILY_LIMIT) {
126
- const resetAt = Date.now() + COOLDOWN_MS;
127
- await redis(["SET", REDIS_RESET_KEY, String(resetAt)]);
128
- }
129
- } catch (redisErr) {
130
- console.warn("Redis increment failed:", redisErr);
131
- }
132
-
133
- return NextResponse.json(data);
134
-
135
- } catch (err) {
136
- console.error("Proxy error:", err);
137
- return NextResponse.json(
138
- { error: "Failed to reach backend" },
139
- { status: 502 }
140
- );
141
- }
142
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/app/globals.css DELETED
@@ -1,80 +0,0 @@
1
- @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap');
2
-
3
- @tailwind base;
4
- @tailwind components;
5
- @tailwind utilities;
6
-
7
- :root {
8
- --bg: #070c12;
9
- --bg2: #0f1923;
10
- --bg3: #19263a;
11
- --bdr: rgba(255,255,255,0.07);
12
- --acc: #00c896;
13
- --acc2: #00ffc8;
14
- --adim: rgba(0,200,150,0.10);
15
- --aglow: rgba(0,200,150,0.28);
16
- --amb: #fbbf24;
17
- --red: #f87171;
18
- --txt: #cdd8ee;
19
- --mut: #4e6580;
20
- }
21
-
22
- * { box-sizing: border-box; margin: 0; padding: 0; }
23
-
24
- html, body {
25
- background: var(--bg);
26
- color: var(--txt);
27
- font-family: 'DM Sans', sans-serif;
28
- min-height: 100vh;
29
- overflow-x: hidden;
30
- }
31
-
32
- /* Scrollbar */
33
- ::-webkit-scrollbar { width: 5px; }
34
- ::-webkit-scrollbar-track { background: transparent; }
35
- ::-webkit-scrollbar-thumb { background: var(--bg3); border-radius: 99px; }
36
-
37
- /* Animations */
38
- @keyframes fadeUp {
39
- from { opacity: 0; transform: translateY(18px); }
40
- to { opacity: 1; transform: translateY(0); }
41
- }
42
- @keyframes shimmer {
43
- 0% { background-position: -200% center; }
44
- 100% { background-position: 200% center; }
45
- }
46
- @keyframes pulse-ring {
47
- 0%, 100% { opacity: 0.5; transform: scale(1); }
48
- 50% { opacity: 1; transform: scale(1.05); }
49
- }
50
- @keyframes scanline {
51
- 0% { transform: translateY(-100%); }
52
- 100% { transform: translateY(100vh); }
53
- }
54
-
55
- .fade-up {
56
- opacity: 0;
57
- animation: fadeUp 0.5s cubic-bezier(0.16,1,0.3,1) forwards;
58
- }
59
-
60
- /* Noise texture overlay */
61
- body::before {
62
- content: '';
63
- position: fixed; inset: 0;
64
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
65
- background-size: 150px;
66
- pointer-events: none;
67
- z-index: 9999;
68
- opacity: 0.4;
69
- }
70
-
71
- /* Gradient mesh background */
72
- body::after {
73
- content: '';
74
- position: fixed; inset: 0;
75
- background:
76
- radial-gradient(ellipse 60% 50% at 80% -10%, rgba(0,200,150,0.06) 0%, transparent 60%),
77
- radial-gradient(ellipse 40% 40% at -10% 80%, rgba(0,150,200,0.04) 0%, transparent 60%);
78
- pointer-events: none;
79
- z-index: 0;
80
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/app/layout.tsx DELETED
@@ -1,15 +0,0 @@
1
- import type { Metadata } from "next";
2
- import "./globals.css";
3
-
4
- export const metadata: Metadata = {
5
- title: "AniFind — AI Anime Recommendations",
6
- description: "Describe a vibe, a mood, a feeling — find your next favourite anime with AI.",
7
- };
8
-
9
- export default function RootLayout({ children }: { children: React.ReactNode }) {
10
- return (
11
- <html lang="en">
12
- <body>{children}</body>
13
- </html>
14
- );
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/app/page.tsx DELETED
@@ -1,744 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useRef, useCallback, useEffect } from "react";
4
- import type { Anime, RecommendationResponse, SearchFilters } from "@/lib/types";
5
-
6
- interface RateLimit {
7
- count: number;
8
- limit: number;
9
- isLocked: boolean;
10
- remainingHrs: number;
11
- remainingMs: number;
12
- }
13
-
14
- // ─── Constants ────────────────────────────────────────────────────────────────
15
-
16
- const SUGGESTIONS = [
17
- { label: "⚔️ Dark fantasy", query: "Dark fantasy with an unreliable narrator" },
18
- { label: "🤖 Cyberpunk", query: "Fast-paced cyberpunk action with great animation" },
19
- { label: "💀 Psychological", query: "Psychological thriller that messes with your head" },
20
- { label: "💘 Romance drama", query: "Bittersweet romance that makes you cry" },
21
- { label: "🌸 Like Naruto", query: "Action packed themed anime similar to Naruto" },
22
- ];
23
-
24
- const ANIME_TYPES = ["Any","TV","Movie","OVA","ONA","Special","Music","CM","PV","TV Special"];
25
-
26
- const GENRES = [
27
- "Any","Action","Adventure","Avant Garde","Award Winning","Boys Love","Comedy",
28
- "Drama","Ecchi","Fantasy","Girls Love","Gourmet","Horror","Mystery","Romance",
29
- "Sci-Fi","Slice of Life","Sports","Supernatural","Suspense",
30
- ];
31
-
32
- // ─── Helpers ─────────────────────────────────────────────────────────────────
33
-
34
- function getImg(images?: Anime["images"]): string {
35
- return images?.webp?.image_url || images?.jpg?.image_url || "";
36
- }
37
-
38
- function scoreColor(score: number | string | undefined): string {
39
- const s = parseFloat(String(score));
40
- if (isNaN(s)) return "#4e6580";
41
- if (s >= 8) return "#00e5a0";
42
- if (s >= 7) return "#fbbf24";
43
- return "#f87171";
44
- }
45
-
46
- // Parses the LLM markdown into [{title, body}] pairs
47
- function parseRecommendations(text: string): { title: string; body: string; pre?: string }[] {
48
- const sections = text.split(/\*\*(.+?)\*\*/);
49
- if (sections.length <= 1) return [];
50
- const pre = sections[0].trim();
51
- const pairs = Array.from({ length: Math.floor((sections.length - 1) / 2) }, (_, i) => ({
52
- title: sections[1 + i * 2].trim(),
53
- body: sections[2 + i * 2].trim().replace(/^[\n—-]+|[\n—-]+$/g, "").trim(),
54
- pre: i === 0 ? pre : undefined,
55
- }));
56
- return pairs;
57
- }
58
-
59
- function matchAnime(title: string, pool: Anime[]): Anime | undefined {
60
- const tl = title.toLowerCase();
61
- return (
62
- pool.find(a => (a.title || "").toLowerCase() === tl) ||
63
- pool.find(a => (a.title_english || "").toLowerCase() === tl) ||
64
- pool.find(a => {
65
- const k = (a.title || "").toLowerCase();
66
- const e = (a.title_english || "").toLowerCase();
67
- return (k && (k.includes(tl) || tl.includes(k))) ||
68
- (e && (e.includes(tl) || tl.includes(e)));
69
- })
70
- );
71
- }
72
-
73
- // ─── Sub-components ───────────────────────────────────────────────────────────
74
-
75
- function SpotlightCard({ anime }: { anime: Anime }) {
76
- const img = getImg(anime.images);
77
- const score = anime.score;
78
- const sc = scoreColor(score);
79
- const synopsis = (anime.synopsis || "").slice(0, 240);
80
- const hasMore = (anime.synopsis || "").length > 240;
81
- const genres = (anime.genres || []).slice(0, 4);
82
- const studios = (anime.studios || []).slice(0, 2).join(", ") || "Unknown Studio";
83
- const engTitle = anime.title_english && anime.title_english !== anime.title ? anime.title_english : null;
84
-
85
- return (
86
- <a
87
- href={anime.mal_url || "#"}
88
- target="_blank"
89
- rel="noopener noreferrer"
90
- className="group flex rounded-2xl overflow-hidden border transition-all duration-300 no-underline"
91
- style={{
92
- background: "var(--bg2)",
93
- borderColor: "var(--bdr)",
94
- color: "inherit",
95
- textDecoration: "none",
96
- }}
97
- onMouseEnter={e => {
98
- (e.currentTarget as HTMLElement).style.borderColor = "rgba(0,200,150,0.4)";
99
- (e.currentTarget as HTMLElement).style.boxShadow = "0 12px 36px rgba(0,200,150,0.12), 0 2px 8px rgba(0,0,0,0.3)";
100
- (e.currentTarget as HTMLElement).style.transform = "translateY(-3px)";
101
- }}
102
- onMouseLeave={e => {
103
- (e.currentTarget as HTMLElement).style.borderColor = "var(--bdr)";
104
- (e.currentTarget as HTMLElement).style.boxShadow = "none";
105
- (e.currentTarget as HTMLElement).style.transform = "translateY(0)";
106
- }}
107
- >
108
- {/* Poster */}
109
- <div className="relative flex-shrink-0 overflow-hidden" style={{ width: 140, minHeight: 195, background: "var(--bg3)" }}>
110
- {img && (
111
- <img
112
- src={img} alt={anime.title}
113
- className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
114
- style={{ display: "block" }}
115
- onError={e => { (e.target as HTMLImageElement).src = "https://placehold.co/280x400/0d1520/00c896?text=?"; }}
116
- />
117
- )}
118
- <div
119
- className="absolute top-2 left-2 text-xs font-bold px-2 py-1 rounded-full"
120
- style={{ background: "rgba(7,12,18,0.85)", color: sc, border: "1px solid rgba(255,255,255,0.1)", backdropFilter: "blur(8px)" }}
121
- >
122
- ★ {score ?? "—"}
123
- </div>
124
- </div>
125
-
126
- {/* Body */}
127
- <div className="flex flex-col gap-2 p-4 flex-1 min-w-0">
128
- {/* Meta chips */}
129
- <div className="flex gap-1 flex-wrap">
130
- {[anime.type, anime.year, `${anime.episodes ?? "?"} ep`, `${Number(anime.scored_by || 0).toLocaleString()} votes`].map((v, i) => v && (
131
- <span key={i} className="text-xs font-bold px-2 py-0.5 rounded-md uppercase tracking-wide" style={{ background: "var(--adim)", color: "var(--acc)", border: "1px solid rgba(0,200,150,0.2)" }}>
132
- {v}
133
- </span>
134
- ))}
135
- </div>
136
-
137
- <div>
138
- <h3 className="font-bold leading-tight" style={{ fontFamily: "'Space Mono', monospace", fontSize: 17, color: "#fff", letterSpacing: "-0.3px" }}>
139
- {anime.title}
140
- </h3>
141
- {engTitle && <p className="text-xs italic mt-0.5" style={{ color: "#7a9bb8" }}>{engTitle}</p>}
142
- <p className="text-xs mt-1" style={{ color: "#6b8299" }}>{studios}</p>
143
- </div>
144
-
145
- <p className="text-sm leading-relaxed flex-1" style={{ color: "var(--txt)", fontSize: 13 }}>
146
- {synopsis}{hasMore && "…"}
147
- </p>
148
-
149
- <div className="flex gap-1 flex-wrap mt-1">
150
- {genres.map(g => (
151
- <span key={g} className="text-xs px-3 py-0.5 rounded-full" style={{ background: "var(--bg3)", border: "1px solid var(--bdr)", color: "var(--txt)" }}>
152
- {g}
153
- </span>
154
- ))}
155
- </div>
156
-
157
- <span
158
- className="text-xs font-bold uppercase tracking-wider px-3 py-2 rounded-lg self-start mt-2 transition-all duration-200 group-hover:text-black"
159
- style={{ background: "var(--adim)", color: "var(--acc)", border: "1px solid rgba(0,200,150,0.3)" }}
160
- onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = "var(--acc)"; (e.currentTarget as HTMLElement).style.color = "#000"; }}
161
- onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = "var(--adim)"; (e.currentTarget as HTMLElement).style.color = "var(--acc)"; }}
162
- >
163
- View on MAL ↗
164
- </span>
165
- </div>
166
- </a>
167
- );
168
- }
169
-
170
- function RetrievedRow({ anime, idx }: { anime: Anime; idx: number }) {
171
- const img = getImg(anime.images);
172
- const raw = anime.final_hybrid_score ?? 0;
173
- const relPct = Math.max(5, Math.round(raw * 100));
174
- const genres = (anime.genres || []).slice(0, 2).join(" · ");
175
-
176
- return (
177
- <a
178
- href={anime.mal_url || "#"}
179
- target="_blank"
180
- rel="noopener noreferrer"
181
- className="grid items-center gap-2 px-2 py-2 rounded-xl transition-all duration-200 no-underline"
182
- style={{ gridTemplateColumns: "22px 40px 1fr auto", color: "inherit", textDecoration: "none" }}
183
- onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = "var(--bg3)"; (e.currentTarget as HTMLElement).style.transform = "translateX(3px)"; }}
184
- onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = "transparent"; (e.currentTarget as HTMLElement).style.transform = "translateX(0)"; }}
185
- >
186
- <span className="text-center font-bold text-xs" style={{ color: "var(--mut)" }}>{String(idx).padStart(2, "0")}</span>
187
- <img
188
- src={img || "https://placehold.co/44x62/0d1520/00c896?text=?"}
189
- alt={anime.title}
190
- className="rounded object-cover"
191
- style={{ width: 40, height: 56 }}
192
- onError={e => { (e.target as HTMLImageElement).src = "https://placehold.co/44x62/0d1520/00c896?text=?"; }}
193
- />
194
- <div className="flex flex-col gap-1 overflow-hidden">
195
- <span className="text-xs font-bold truncate" style={{ color: "#fff" }}>{anime.title}</span>
196
- <span className="text-xs truncate" style={{ color: "#6b8299" }}>{anime.type} · {anime.year} · {genres}</span>
197
- <div className="h-1 rounded-full overflow-hidden" style={{ background: "var(--bg3)" }}>
198
- <div className="h-full rounded-full" style={{ width: `${relPct}%`, background: "linear-gradient(90deg, var(--acc), var(--acc2))" }} />
199
- </div>
200
- </div>
201
- <div className="flex flex-col items-end gap-1">
202
- <span className="text-xs font-bold" style={{ color: "var(--amb)" }}>★ {anime.score ?? "—"}</span>
203
- <span className="text-xs" style={{ color: "var(--mut)" }}>{relPct}%</span>
204
- </div>
205
- </a>
206
- );
207
- }
208
-
209
- // ─── Main Page ────────────────────────────────────────────────────────────────
210
-
211
- export default function Home() {
212
- const [query, setQuery] = useState("");
213
- const [animeType, setAnimeType] = useState("Any");
214
- const [genres, setGenres] = useState<string[]>(["Any"]);
215
- const [minScore, setMinScore] = useState(7.5);
216
- const [nResults, setNResults] = useState(3);
217
- const [loading, setLoading] = useState(false);
218
- const [result, setResult] = useState<RecommendationResponse | null>(null);
219
- const [error, setError] = useState<string | null>(null);
220
- const [filtersOpen, setFiltersOpen] = useState(false);
221
- const [rateLimit, setRateLimit] = useState<RateLimit>({ count: 0, limit: 10, isLocked: false, remainingHrs: 0, remainingMs: 0 });
222
- const inputRef = useRef<HTMLTextAreaElement>(null);
223
-
224
- // Poll rate limit counter every 15 seconds
225
- useEffect(() => {
226
- const fetchLimit = async () => {
227
- try {
228
- const res = await fetch("/api/recommend");
229
- if (res.ok) setRateLimit(await res.json());
230
- } catch { /* silent fail */ }
231
- };
232
- fetchLimit();
233
- const interval = setInterval(fetchLimit, 15000);
234
- return () => clearInterval(interval);
235
- }, []);
236
-
237
- const handleSearch = useCallback(async (q?: string) => {
238
- const searchQuery = (q ?? query).trim();
239
- if (!searchQuery) return;
240
-
241
- // Block immediately if we already know it's locked
242
- if (rateLimit.isLocked) {
243
- setError(`Global search limit reached. Resets in ${rateLimit.remainingHrs} hr${rateLimit.remainingHrs !== 1 ? "s" : ""}.`);
244
- return;
245
- }
246
-
247
- setLoading(true);
248
- setError(null);
249
- setResult(null);
250
-
251
- try {
252
- const payload: SearchFilters = { query: searchQuery, n_results: nResults };
253
- if (minScore > 0) payload.min_score = minScore;
254
- if (!genres.includes("Any") && genres.length > 0) payload.genre_filter = genres;
255
- if (animeType !== "Any") payload.anime_type = animeType;
256
-
257
- const res = await fetch("/api/recommend", {
258
- method: "POST",
259
- headers: { "Content-Type": "application/json" },
260
- body: JSON.stringify(payload),
261
- });
262
-
263
- // Refresh counter after every attempt (success or 429)
264
- fetch("/api/recommend").then(r => r.ok ? r.json() : null).then(d => { if (d) setRateLimit(d); }).catch(() => {});
265
-
266
- if (!res.ok) {
267
- const err = await res.json().catch(() => ({ error: "Unknown error" }));
268
- throw new Error(err.error || `HTTP ${res.status}`);
269
- }
270
-
271
- const data: RecommendationResponse = await res.json();
272
- setResult(data);
273
- } catch (e) {
274
- setError(e instanceof Error ? e.message : "Something went wrong");
275
- } finally {
276
- setLoading(false);
277
- }
278
- }, [query, animeType, genres, minScore, nResults, rateLimit]);
279
-
280
- const handleChipClick = (q: string) => {
281
- setQuery(q);
282
- handleSearch(q);
283
- inputRef.current?.focus();
284
- };
285
-
286
- const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
287
- if (e.key === "Enter" && !e.shiftKey) {
288
- e.preventDefault();
289
- handleSearch();
290
- }
291
- };
292
-
293
- const toggleGenre = (g: string) => {
294
- if (g === "Any") { setGenres(["Any"]); return; }
295
- setGenres(prev => {
296
- const next = prev.filter(x => x !== "Any");
297
- return next.includes(g) ? (next.filter(x => x !== g) || ["Any"]) : [...next, g];
298
- });
299
- };
300
-
301
- const parsedRecs = result ? parseRecommendations(result.recommendations) : [];
302
- const retrievedAnimes = result?.retrieved_animes ?? [];
303
- const timeStr = result?.metadata["Time taken for LLM + vector search"]?.slice(0, 5) ?? "—";
304
-
305
- return (
306
- <div className="flex min-h-screen relative z-10">
307
-
308
- {/* ── Fixed Left Sidebar ─────────────────────────────── */}
309
- <aside
310
- className="fixed top-0 left-0 h-full flex flex-col overflow-y-auto"
311
- style={{
312
- width: 230,
313
- background: "var(--bg)",
314
- borderRight: "1px solid var(--bdr)",
315
- padding: "32px 20px",
316
- zIndex: 50,
317
- scrollbarWidth: "none",
318
- boxShadow: "4px 0 24px rgba(0,0,0,0.25)",
319
- }}
320
- >
321
- {/* Logo */}
322
- <div className="mb-6">
323
- <div className="flex items-center gap-3 mb-2">
324
- <div
325
- className="flex items-center justify-center rounded-xl flex-shrink-0"
326
- style={{
327
- width: 44, height: 44, fontSize: 24,
328
- background: "linear-gradient(135deg, var(--acc), var(--acc2))",
329
- color: "#070c12",
330
- boxShadow: "0 0 0 1px rgba(0,200,150,0.3), 0 4px 20px var(--aglow)",
331
- }}
332
- >
333
-
334
- </div>
335
- <div>
336
- <div style={{ fontFamily: "'Space Mono', monospace", fontSize: 22, fontWeight: 700, color: "#fff", letterSpacing: "-0.5px", lineHeight: 1 }}>
337
- Ani<span style={{ color: "var(--acc)" }}>Find</span>
338
- </div>
339
- <div style={{ fontFamily: "'Space Mono', monospace", fontSize: 10, color: "var(--acc)", letterSpacing: "2px", marginTop: 3, opacity: 0.7 }}>
340
- AI · ANIME · RAG
341
- </div>
342
- </div>
343
- </div>
344
- {/* Accent underline */}
345
- <div style={{ height: 1, background: "linear-gradient(90deg, var(--acc), transparent)", marginTop: 8, opacity: 0.4 }} />
346
- </div>
347
-
348
- <p className="text-sm leading-relaxed mb-8" style={{ color: "#8faabf" }}>
349
- Describe a vibe, a mood — AI matches it to the perfect title.
350
- </p>
351
-
352
- <p className="text-xs font-bold uppercase tracking-widest mb-3" style={{ color: "var(--mut)", letterSpacing: "1.4px" }}>
353
- Quick searches
354
- </p>
355
-
356
- <div className="flex flex-col gap-2">
357
- {SUGGESTIONS.map(s => (
358
- <button
359
- key={s.query}
360
- onClick={() => handleChipClick(s.query)}
361
- className="text-left text-sm font-semibold px-3 py-2.5 rounded-xl transition-all duration-300"
362
- style={{ background: "var(--bg2)", border: "1px solid var(--bdr)", color: "var(--txt)", cursor: "pointer" }}
363
- onMouseEnter={e => {
364
- (e.currentTarget as HTMLElement).style.color = "var(--acc)";
365
- (e.currentTarget as HTMLElement).style.background = "var(--bg3)";
366
- (e.currentTarget as HTMLElement).style.borderColor = "rgba(0,200,150,0.4)";
367
- (e.currentTarget as HTMLElement).style.transform = "translateX(4px)";
368
- }}
369
- onMouseLeave={e => {
370
- (e.currentTarget as HTMLElement).style.color = "var(--txt)";
371
- (e.currentTarget as HTMLElement).style.background = "var(--bg2)";
372
- (e.currentTarget as HTMLElement).style.borderColor = "var(--bdr)";
373
- (e.currentTarget as HTMLElement).style.transform = "translateX(0)";
374
- }}
375
- >
376
- {s.label}
377
- </button>
378
- ))}
379
- </div>
380
-
381
- {/* Global rate limit counter */}
382
- <div className="mt-auto pt-6">
383
- <div
384
- className="rounded-xl p-3"
385
- style={{
386
- background: rateLimit.isLocked ? "rgba(248,113,113,0.08)" : "var(--bg2)",
387
- border: `1px solid ${rateLimit.isLocked ? "rgba(248,113,113,0.3)" : "var(--bdr)"}`,
388
- }}
389
- >
390
- <div className="flex justify-between items-center mb-2">
391
- <span className="text-xs font-bold uppercase tracking-widest" style={{ color: "var(--mut)", letterSpacing: "1px" }}>
392
- Global searches
393
- </span>
394
- <span
395
- className="text-xs font-bold px-2 py-0.5 rounded-full"
396
- style={{
397
- background: rateLimit.isLocked ? "rgba(248,113,113,0.15)" : rateLimit.count >= 8 ? "rgba(251,191,36,0.12)" : "var(--adim)",
398
- color: rateLimit.isLocked ? "var(--red)" : rateLimit.count >= 8 ? "var(--amb)" : "var(--acc)",
399
- }}
400
- >
401
- {rateLimit.count}/{rateLimit.limit}
402
- </span>
403
- </div>
404
-
405
- {/* Progress bar */}
406
- <div className="rounded-full overflow-hidden" style={{ height: 4, background: "var(--bg3)" }}>
407
- <div
408
- className="h-full rounded-full transition-all duration-500"
409
- style={{
410
- width: `${(rateLimit.count / rateLimit.limit) * 100}%`,
411
- background: rateLimit.isLocked
412
- ? "var(--red)"
413
- : rateLimit.count >= 8
414
- ? "var(--amb)"
415
- : "linear-gradient(90deg, var(--acc), var(--acc2))",
416
- }}
417
- />
418
- </div>
419
-
420
- {rateLimit.isLocked ? (
421
- <p className="text-xs mt-2" style={{ color: "var(--red)" }}>
422
- Locked · resets in {rateLimit.remainingHrs}h
423
- </p>
424
- ) : (
425
- <p className="text-xs mt-2" style={{ color: "var(--mut)" }}>
426
- {rateLimit.limit - rateLimit.count} remaining · resets after {rateLimit.limit} total
427
- </p>
428
- )}
429
- </div>
430
-
431
- {/* Footer credit */}
432
- <div className="pt-4 text-xs" style={{ color: "var(--mut)" }}>
433
- Powered by Qdrant + Supabase<br />
434
- <span style={{ color: "rgba(0,200,150,0.6)" }}>RAG · Cross-Encoder · LLM</span>
435
- </div>
436
- </div>
437
- </aside>
438
-
439
- {/* ── Main Content ───────────────────────────────────── */}
440
- <main className="flex flex-col flex-1" style={{ marginLeft: 230 }}>
441
-
442
- {/* Sticky Search Bar */}
443
- <div
444
- className="sticky top-0 z-40 flex items-center gap-3 px-6 py-4"
445
- style={{
446
- background: "rgba(7,12,18,0.88)",
447
- backdropFilter: "blur(20px)",
448
- borderBottom: "1px solid var(--bdr)",
449
- }}
450
- >
451
- <div
452
- className="flex-1 flex items-start rounded-2xl transition-all duration-300"
453
- style={{
454
- background: "var(--bg2)",
455
- border: `2px solid ${rateLimit.isLocked ? "rgba(248,113,113,0.3)" : "var(--bdr)"}`,
456
- opacity: rateLimit.isLocked ? 0.6 : 1,
457
- }}
458
- onFocusCapture={e => { if (!rateLimit.isLocked) { (e.currentTarget as HTMLElement).style.borderColor = "var(--acc)"; (e.currentTarget as HTMLElement).style.boxShadow = "0 0 0 3px var(--adim)"; } }}
459
- onBlurCapture={e => { (e.currentTarget as HTMLElement).style.borderColor = rateLimit.isLocked ? "rgba(248,113,113,0.3)" : "var(--bdr)"; (e.currentTarget as HTMLElement).style.boxShadow = "none"; }}
460
- >
461
- <textarea
462
- ref={inputRef}
463
- rows={1}
464
- value={query}
465
- onChange={e => setQuery(e.target.value)}
466
- onKeyDown={handleKeyDown}
467
- disabled={rateLimit.isLocked}
468
- placeholder={rateLimit.isLocked ? `Search locked — resets in ${rateLimit.remainingHrs}h` : "e.g. A melancholic sci-fi about memory and loss..."}
469
- className="flex-1 resize-none bg-transparent outline-none px-4 py-3 text-sm"
470
- style={{ color: rateLimit.isLocked ? "var(--red)" : "#fff", fontFamily: "DM Sans, sans-serif", fontSize: 14, lineHeight: 1.5, cursor: rateLimit.isLocked ? "not-allowed" : "text" }}
471
- />
472
- </div>
473
-
474
- {/* Filter toggle */}
475
- <button
476
- onClick={() => setFiltersOpen(o => !o)}
477
- className="px-4 py-3 rounded-xl text-sm font-semibold transition-all duration-200"
478
- style={{
479
- background: filtersOpen ? "var(--adim)" : "var(--bg2)",
480
- border: `1px solid ${filtersOpen ? "rgba(0,200,150,0.4)" : "var(--bdr)"}`,
481
- color: filtersOpen ? "var(--acc)" : "var(--txt)",
482
- cursor: "pointer",
483
- }}
484
- >
485
- ⚙ Filters
486
- </button>
487
-
488
- {/* Search button */}
489
- <button
490
- onClick={() => handleSearch()}
491
- disabled={loading}
492
- className="flex items-center justify-center rounded-xl font-black text-lg transition-all duration-300"
493
- style={{
494
- width: 48, height: 48,
495
- background: rateLimit.isLocked
496
- ? "rgba(248,113,113,0.2)"
497
- : loading ? "rgba(0,200,150,0.3)"
498
- : "linear-gradient(135deg, var(--acc), var(--acc2))",
499
- color: rateLimit.isLocked ? "var(--red)" : "#000",
500
- border: rateLimit.isLocked ? "1px solid rgba(248,113,113,0.3)" : "none",
501
- cursor: (loading || rateLimit.isLocked) ? "not-allowed" : "pointer",
502
- boxShadow: (loading || rateLimit.isLocked) ? "none" : "0 4px 16px rgba(0,200,150,0.4)",
503
- flexShrink: 0,
504
- }}
505
- onMouseEnter={e => { if (!loading && !rateLimit.isLocked) { (e.currentTarget as HTMLElement).style.transform = "translateY(-2px) scale(1.05)"; (e.currentTarget as HTMLElement).style.boxShadow = "0 8px 24px rgba(0,200,150,0.5)"; } }}
506
- onMouseLeave={e => { (e.currentTarget as HTMLElement).style.transform = "none"; (e.currentTarget as HTMLElement).style.boxShadow = (loading || rateLimit.isLocked) ? "none" : "0 4px 16px rgba(0,200,150,0.4)"; }}
507
- >
508
- {rateLimit.isLocked ? "✕" : loading ? "⟳" : "➔"}
509
- </button>
510
- </div>
511
-
512
- {/* Filters Panel */}
513
- {filtersOpen && (
514
- <div
515
- className="px-6 py-5 flex flex-wrap gap-6 items-start"
516
- style={{ background: "var(--bg2)", borderBottom: "1px solid var(--bdr)" }}
517
- >
518
- {/* Anime Type */}
519
- <div>
520
- <label className="block text-xs font-bold uppercase tracking-widest mb-2" style={{ color: "var(--mut)" }}>Type</label>
521
- <div className="flex flex-wrap gap-1">
522
- {ANIME_TYPES.map(t => (
523
- <button
524
- key={t}
525
- onClick={() => setAnimeType(t)}
526
- className="text-xs font-semibold px-3 py-1 rounded-lg transition-all duration-150"
527
- style={{
528
- background: animeType === t ? "var(--adim)" : "var(--bg3)",
529
- border: `1px solid ${animeType === t ? "rgba(0,200,150,0.4)" : "var(--bdr)"}`,
530
- color: animeType === t ? "var(--acc)" : "var(--txt)",
531
- cursor: "pointer",
532
- }}
533
- >
534
- {t}
535
- </button>
536
- ))}
537
- </div>
538
- </div>
539
-
540
- {/* Genre */}
541
- <div>
542
- <label className="block text-xs font-bold uppercase tracking-widest mb-2" style={{ color: "var(--mut)" }}>Genre</label>
543
- <div className="flex flex-wrap gap-1" style={{ maxWidth: 420 }}>
544
- {GENRES.map(g => {
545
- const active = genres.includes(g);
546
- return (
547
- <button
548
- key={g}
549
- onClick={() => toggleGenre(g)}
550
- className="text-xs font-semibold px-3 py-1 rounded-lg transition-all duration-150"
551
- style={{
552
- background: active ? "var(--adim)" : "var(--bg3)",
553
- border: `1px solid ${active ? "rgba(0,200,150,0.4)" : "var(--bdr)"}`,
554
- color: active ? "var(--acc)" : "var(--txt)",
555
- cursor: "pointer",
556
- }}
557
- >
558
- {g}
559
- </button>
560
- );
561
- })}
562
- </div>
563
- </div>
564
-
565
- {/* Min Score */}
566
- <div style={{ minWidth: 180 }}>
567
- <label className="block text-xs font-bold uppercase tracking-widest mb-2" style={{ color: "var(--mut)" }}>
568
- Min Rating: <span style={{ color: "var(--acc)" }}>{minScore > 0 ? minScore : "Any"}</span>
569
- </label>
570
- <input
571
- type="range" min={0} max={10} step={0.5} value={minScore}
572
- onChange={e => setMinScore(parseFloat(e.target.value))}
573
- className="w-full accent-emerald-400"
574
- />
575
- </div>
576
-
577
- {/* N Results */}
578
- <div style={{ minWidth: 160 }}>
579
- <label className="block text-xs font-bold uppercase tracking-widest mb-2" style={{ color: "var(--mut)" }}>
580
- Results: <span style={{ color: "var(--acc)" }}>{nResults}</span>
581
- </label>
582
- <input
583
- type="range" min={1} max={8} step={1} value={nResults}
584
- onChange={e => setNResults(parseInt(e.target.value))}
585
- className="w-full accent-emerald-400"
586
- />
587
- </div>
588
- </div>
589
- )}
590
-
591
- {/* Output Zone */}
592
- <div className="flex-1 px-6 py-6">
593
-
594
- {/* HUD Bar */}
595
- {(loading || result || error) && (
596
- <div
597
- className="flex items-center justify-between rounded-xl px-5 py-3 mb-6"
598
- style={{ background: "var(--bg2)", border: "1px solid var(--bdr)" }}
599
- >
600
- {loading && (
601
- <div className="flex items-center gap-3">
602
- <div className="w-2 h-2 rounded-full" style={{ background: "var(--acc)", animation: "pulse-ring 1.2s ease-in-out infinite" }} />
603
- <span className="text-sm font-semibold" style={{ color: "var(--acc)" }}>Searching through anime universe…</span>
604
- </div>
605
- )}
606
- {error && (
607
- <span className="text-sm font-bold px-4 py-1.5 rounded-full" style={{ background: "rgba(248,113,113,0.1)", color: "var(--red)", border: "1px solid rgba(248,113,113,0.2)" }}>
608
- ✗ {error}
609
- </span>
610
- )}
611
- {result && !loading && (
612
- <>
613
- <span className="text-sm font-bold px-4 py-1.5 rounded-full" style={{ background: "var(--adim)", color: "var(--acc)", border: "1px solid rgba(0,200,150,0.3)" }}>
614
- ✦ Results ready
615
- </span>
616
- <div className="flex items-center gap-5">
617
- <div className="flex flex-col items-end">
618
- <span className="font-black text-lg leading-none" style={{ color: "#fff", fontFamily: "'Space Mono', monospace" }}>{result.retrieved_count}</span>
619
- <span className="text-xs uppercase tracking-wider mt-0.5" style={{ color: "var(--mut)" }}>titles scanned</span>
620
- </div>
621
- <div className="w-px h-7" style={{ background: "var(--bdr)" }} />
622
- <div className="flex flex-col items-end">
623
- <span className="font-black text-lg leading-none" style={{ color: "#fff", fontFamily: "'Space Mono', monospace" }}>{timeStr}s</span>
624
- <span className="text-xs uppercase tracking-wider mt-0.5" style={{ color: "var(--mut)" }}>search time</span>
625
- </div>
626
- </div>
627
- </>
628
- )}
629
- </div>
630
- )}
631
-
632
- {/* Empty state */}
633
- {!loading && !result && !error && (
634
- <div className="flex flex-col items-center justify-center py-24 gap-4 text-center">
635
- <div
636
- className="text-5xl mb-2"
637
- style={{ filter: "drop-shadow(0 4px 16px var(--aglow))", animation: "pulse-ring 3s ease-in-out infinite" }}
638
- >
639
-
640
- </div>
641
- <h2 className="text-2xl font-black" style={{ fontFamily: "'Space Mono', monospace", color: "#fff" }}>
642
- What are you in the mood for?
643
- </h2>
644
- <p className="text-sm max-w-xs" style={{ color: "var(--mut)" }}>
645
- Type a vibe, a feeling, or a genre above — or pick a quick search from the sidebar.
646
- </p>
647
- </div>
648
- )}
649
-
650
- {/* Loading skeleton */}
651
- {loading && (
652
- <div className="flex gap-6">
653
- <div className="flex-1 flex flex-col gap-4">
654
- {Array.from({ length: nResults }).map((_, i) => (
655
- <div key={i} className="flex rounded-2xl overflow-hidden" style={{ background: "var(--bg2)", border: "1px solid var(--bdr)", height: 200, opacity: 1 - i * 0.2 }}>
656
- <div className="flex-shrink-0" style={{ width: 140, background: "var(--bg3)" }} />
657
- <div className="flex-1 p-4 flex flex-col gap-3">
658
- <div className="flex gap-2">
659
- {[60, 50, 80, 90].map((w, j) => <div key={j} className="rounded-md h-5" style={{ width: w, background: "var(--bg3)" }} />)}
660
- </div>
661
- <div className="rounded-lg h-5 w-3/4" style={{ background: "var(--bg3)" }} />
662
- <div className="rounded-lg h-4 w-1/3" style={{ background: "var(--bg3)" }} />
663
- <div className="flex-1 rounded-lg" style={{ background: "var(--bg3)" }} />
664
- </div>
665
- </div>
666
- ))}
667
- </div>
668
- <div className="w-72 rounded-2xl flex-shrink-0" style={{ background: "var(--bg2)", border: "1px solid var(--bdr)", height: 400 }} />
669
- </div>
670
- )}
671
-
672
- {/* Results */}
673
- {result && !loading && (
674
- <div className="flex gap-6 items-start">
675
-
676
- {/* Recommendations column */}
677
- <div className="flex-1 min-w-0">
678
- <p className="text-xs font-bold uppercase tracking-widest mb-5 pb-3" style={{ color: "var(--mut)", borderBottom: "1px solid var(--bdr)", letterSpacing: "1.5px" }}>
679
- Recommendations
680
- </p>
681
-
682
- {parsedRecs.length === 0 ? (
683
- <p className="text-sm leading-relaxed px-1" style={{ color: "#b0c8e0", fontSize: 15 }}>{result.recommendations}</p>
684
- ) : (
685
- parsedRecs.map((rec, i) => {
686
- const anime = matchAnime(rec.title, retrievedAnimes);
687
- return (
688
- <div
689
- key={i}
690
- className="fade-up rounded-2xl overflow-hidden mb-5"
691
- style={{
692
- animationDelay: `${i * 0.1}s`,
693
- border: "1px solid var(--bdr)",
694
- background: "var(--bg2)",
695
- }}
696
- >
697
- {rec.pre && i === 0 && (
698
- <p className="px-5 pt-4 pb-2 text-sm italic" style={{ color: "#b0c8e0" }}>{rec.pre}</p>
699
- )}
700
- {anime
701
- ? <SpotlightCard anime={anime} />
702
- : <div className="px-5 py-4 font-bold" style={{ fontFamily: "'Space Mono', monospace", color: "#fff" }}>{rec.title}</div>
703
- }
704
- <div className="px-5 py-4 text-sm leading-relaxed" style={{ borderTop: "1px solid var(--bdr)", background: "rgba(255,255,255,0.015)", color: "#b8cde0" }}>
705
- {rec.body}
706
- </div>
707
- </div>
708
- );
709
- })
710
- )}
711
- </div>
712
-
713
- {/* Retrieved sidebar */}
714
- {retrievedAnimes.length > 0 && (
715
- <aside
716
- className="flex-shrink-0 rounded-2xl p-4 sticky overflow-y-auto"
717
- style={{
718
- width: 280,
719
- top: 80,
720
- maxHeight: "calc(100vh - 120px)",
721
- background: "var(--bg2)",
722
- border: "1px solid var(--bdr)",
723
- scrollbarWidth: "thin",
724
- scrollbarColor: "var(--bg3) transparent",
725
- }}
726
- >
727
- <div className="flex justify-between items-center mb-4 pb-3" style={{ borderBottom: "1px solid var(--bdr)" }}>
728
- <span className="text-xs font-bold uppercase tracking-widest" style={{ color: "var(--mut)" }}>Vector search</span>
729
- <span className="text-xs font-bold px-2 py-0.5 rounded-full" style={{ background: "var(--adim)", color: "var(--acc)" }}>{result.retrieved_count}</span>
730
- </div>
731
- <div className="flex flex-col gap-1">
732
- {retrievedAnimes.map((a, i) => (
733
- <RetrievedRow key={i} anime={a} idx={i + 1} />
734
- ))}
735
- </div>
736
- </aside>
737
- )}
738
- </div>
739
- )}
740
- </div>
741
- </main>
742
- </div>
743
- );
744
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/gradio_app.py DELETED
@@ -1,773 +0,0 @@
1
- import gradio as gr
2
- from gradio import themes
3
- import requests
4
- import os
5
- import re
6
- from config import settings
7
-
8
- API_URL = os.getenv("API_URL", "http://127.0.0.1:8000")
9
-
10
- ANIME_TYPES = ["Any", "TV", "Movie", "OVA", "ONA",
11
- "Special", "Music", "CM", "PV", "TV Special"]
12
- GENRES = [
13
- "Any", "Action", "Adventure", "Avant Garde", "Award Winning", "Boys Love",
14
- "Comedy", "Drama", "Ecchi", "Erotica", "Fantasy", "Girls Love", "Gourmet",
15
- "Hentai", "Horror", "Mystery", "Romance", "Sci-Fi", "Slice of Life", "Sports",
16
- "Supernatural", "Suspense"
17
- ]
18
-
19
- SUGGESTIONS = [
20
- {"label": "⚔️ Dark fantasy",
21
- "query": "Dark fantasy with an unreliable narrator"},
22
- {"label": "🤖 Cyberpunk action",
23
- "query": "Fast-paced cyberpunk action with great animation"},
24
- {"label": "💀 Psychological",
25
- "query": "Psychological thriller that messes with your head"},
26
- {"label": "💘 Romance drama", "query": "Bittersweet romance that makes you cry"},
27
- {"label": "🌸 Similar to Naruto",
28
- "query": "Action packed themed anime similar to Naruto"}
29
- ]
30
-
31
- # ─── HTML builders ────────────────────────────────────────────────────────────
32
-
33
-
34
- def _img(images: dict) -> str:
35
- webp = images.get("webp", {}).get("image_url", "")
36
- jpg = images.get("jpg", {}).get("image_url", "")
37
- return webp or jpg or ""
38
-
39
-
40
- def build_spotlight_card(anime: dict) -> str:
41
- img = _img(anime.get("images", {}))
42
- title = anime.get("title", "Unknown")
43
- eng = anime.get("title_english") or ""
44
- score = anime.get("score") or "—"
45
- scored_by = anime.get("scored_by") or "—"
46
- year = anime.get("year") or "—"
47
- kind = anime.get("type") or "—"
48
- eps = anime.get("episodes") or "—"
49
- genres = anime.get("genres") or []
50
- studios = anime.get("studios") or []
51
- url = anime.get("mal_url", "#")
52
- raw_syn = anime.get("synopsis") or ""
53
- synopsis = raw_syn[:220].rstrip() + ("..." if len(raw_syn) > 220 else "")
54
-
55
- genre_pills = "".join(f"<span class='pill'>{g}</span>" for g in genres[:4])
56
- studio_str = ", ".join(studios[:2]) if studios else "Unknown Studio"
57
-
58
- eng_html = f"<h4 class='sc-eng'>{eng}</h4>" if eng and eng.strip(
59
- ) and eng != title else ""
60
-
61
- try:
62
- s = float(score)
63
- sc = "#00e5a0" if s >= 8 else "#fbbf24" if s >= 7 else "#f87171"
64
- except Exception:
65
- sc = "#6b7a99"
66
-
67
- return (
68
- f"<a href='{url}' target='_blank' class='sc'>"
69
- f"<div class='sc-poster'>"
70
- f"<img src='{img}' alt='{title}' loading='lazy' "
71
- f"onerror=\"this.src='https://placehold.co/280x400/0d1520/00c896?text=?'\"/>"
72
- f"<div class='sc-badge' style='color:{sc}'>&#9733; {score}</div>"
73
- f"</div>"
74
- f"<div class='sc-body'>"
75
- f"<div class='sc-chips'>"
76
- f"<span class='mc'>{kind}</span><span class='mc'>{year}</span><span class='mc'>{eps} ep</span><span class='mc'>Scored by:{scored_by}</span>"
77
- f"</div>"
78
- f"<h3 class='sc-title'>{title}</h3>"
79
- f"{eng_html}"
80
- f"<p class='sc-studio'>{studio_str}</p>"
81
- f"<p class='sc-synopsis'>{synopsis}</p>"
82
- f"<div class='pill-row'>{genre_pills}</div>"
83
- f"<span class='mal-btn'>View on MAL &#8599;</span>"
84
- f"</div></a>"
85
- )
86
-
87
-
88
- def build_retrieved_row(anime: dict, idx: int) -> str:
89
- img = _img(anime.get("images", {}))
90
- title = anime.get("title", "Unknown")
91
- score = anime.get("score") or "—"
92
- year = anime.get("year") or "—"
93
- kind = anime.get("type") or ""
94
- genres = (anime.get("genres") or [])[:2]
95
- url = anime.get("mal_url", "#")
96
- raw_score = anime.get("final_hybrid_score", 0)
97
- rel_pct = max(5, int(raw_score * 100))
98
- genre_str = " · ".join(genres)
99
-
100
- return (
101
- f"<a href='{url}' target='_blank' class='rr'>"
102
- f"<span class='rr-idx'>{idx:02d}</span>"
103
- f"<img src='{img}' alt='{title}' class='rr-thumb' loading='lazy' "
104
- f"onerror=\"this.src='https://placehold.co/44x62/0d1520/00c896?text=?'\"/>"
105
- f"<div class='rr-info'>"
106
- f"<span class='rr-title'>{title}</span>"
107
- f"<span class='rr-sub'>{kind} · {year} · {genre_str}</span>"
108
- f"<div class='rr-bar-bg'><div class='rr-bar-fill' style='width:{rel_pct}%'></div></div>"
109
- f"</div>"
110
- f"<div class='rr-right'>"
111
- f"<span class='rr-score'>&#9733; {score}</span>"
112
- f"<span class='rr-pct'>{rel_pct}%</span>"
113
- f"</div>"
114
- f"</a>"
115
- )
116
-
117
-
118
- def format_output(result: dict) -> tuple[str, str]:
119
- recs_text = result.get("recommendations", "")
120
- retrieved_count = result.get("retrieved_count", 0)
121
- time_str = result.get("metadata", {}).get(
122
- "Time taken for LLM + vector search", "?")
123
- retrieved = result.get("retrieved_animes", [])
124
-
125
- header = (
126
- "<div class='hud'>"
127
- "<div class='hud-pill ready'>&#10022; Results ready</div>"
128
- "<div class='hud-stats'>"
129
- f"<div class='hud-stat'><span class='hud-n'>{retrieved_count}</span>"
130
- f"<span class='hud-l'>titles scanned</span></div>"
131
- "<div class='hud-sep'></div>"
132
- f"<div class='hud-stat'><span class='hud-n'>{time_str[:4]}s</span>"
133
- f"<span class='hud-l'>search time</span></div>"
134
- "</div></div>"
135
- )
136
-
137
- sections = re.split(r'\*\*(.+?)\*\*', recs_text)
138
- title_map = {a.get("title", "").lower(): a for a in retrieved}
139
- title_map.update(
140
- {(a.get("title_english") or "").lower(): a for a in retrieved})
141
-
142
- recs_html = ""
143
- if len(sections) > 1:
144
- pre = sections[0].strip()
145
- if pre:
146
- recs_html += f"<p class='llm-intro'>{pre}</p>"
147
-
148
- pairs = list(zip(sections[1::2], sections[2::2]))
149
- for i, (rec_title, rec_body) in enumerate(pairs):
150
- rec_title = rec_title.strip()
151
- rec_body = rec_body.strip().lstrip("\n").rstrip("\n—-").strip()
152
-
153
- matched = title_map.get(rec_title.lower())
154
- if not matched:
155
- for k, v in title_map.items():
156
- if k and (k in rec_title.lower() or rec_title.lower() in k):
157
- matched = v
158
- break
159
-
160
- card = build_spotlight_card(
161
- matched) if matched else f"<div class='no-img-title'>{rec_title}</div>"
162
- delay = f"{i * 0.09:.2f}"
163
- recs_html += (
164
- f"<div class='rec-block' style='animation-delay:{delay}s'>"
165
- f"{card}"
166
- f"<div class='llm-pitch'>{rec_body}</div>"
167
- f"</div>"
168
- )
169
- else:
170
- recs_html = f"<div class='convo-msg'>{recs_text}</div>"
171
-
172
- sidebar = ""
173
- if retrieved:
174
- rows = "".join(build_retrieved_row(a, i + 1)
175
- for i, a in enumerate(retrieved))
176
- sidebar = (
177
- "<aside class='sidebar'>"
178
- "<div class='sidebar-head'>"
179
- "<span class='sidebar-label'>Vector search</span>"
180
- f"<span class='sidebar-count'>{retrieved_count}</span>"
181
- "</div>"
182
- f"<div class='rr-list'>{rows}</div>"
183
- "</aside>"
184
- )
185
-
186
- body = (
187
- "<div class='main-layout'>"
188
- "<section class='recs-col'>"
189
- "<div class='recs-heading'>Recommendations</div>"
190
- f"{recs_html}"
191
- "</section>"
192
- f"{sidebar}"
193
- "</div>"
194
- )
195
- return header, body
196
-
197
-
198
- def get_recommendations(query, min_score, genre_filter, anime_type, n_results):
199
- if not query or not query.strip():
200
- return (
201
- "<div class='hud'><div class='hud-pill idle'>Waiting for your prompt...</div></div>",
202
- ""
203
- )
204
- try:
205
- payload = {"query": query.strip(), "n_results": int(n_results)}
206
- if min_score > 0:
207
- payload["min_score"] = float(min_score)
208
- if genre_filter and "Any" not in genre_filter:
209
- payload["genre_filter"] = genre_filter
210
- if anime_type and anime_type != "Any":
211
- payload["anime_type"] = anime_type
212
-
213
- response = requests.post(
214
- f"{API_URL}/recommend",
215
- json=payload,
216
- headers={"X-API-Key": settings.frontend_api_key},
217
- timeout=30)
218
- response.raise_for_status()
219
- result = response.json()
220
-
221
- if not result.get("recommendations", "").strip():
222
- return (
223
- "<div class='hud'><div class='hud-pill err'>No matches</div></div>",
224
- "<p class='convo-msg'>Couldn't find anything for that vibe. Try different keywords.</p>"
225
- )
226
- return format_output(result)
227
-
228
- except requests.exceptions.ConnectionError:
229
- return (
230
- "<div class='hud'><div class='hud-pill err'>Offline</div></div>",
231
- "<p class='convo-msg'>Cannot reach the backend. Is FastAPI running?</p>"
232
- )
233
- except Exception as e:
234
- return (
235
- "<div class='hud'><div class='hud-pill err'>Error</div></div>",
236
- f"<p class='convo-msg'>Something went wrong: {e}</p>"
237
- )
238
-
239
-
240
- # =============================================================================
241
- # CSS (SCALED DOWN TO "AVERAGE" SIZE)
242
- # =============================================================================
243
- CSS = """
244
- @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap');
245
-
246
- :root {
247
- --bg: #070c12;
248
- --bg2: #121b29;
249
- --bg3: #1a2639;
250
- --bdr: rgba(255,255,255,0.08);
251
- --acc: #00c896;
252
- --acc2: #00e8b0;
253
- --adim: rgba(0,200,150,0.12);
254
- --aglow: rgba(0,200,150,0.3);
255
- --amb: #fbbf24;
256
- --red: #f87171;
257
- --txt: #dde3f0;
258
- --mut: #6b8299;
259
- --f: 'Plus Jakarta Sans', sans-serif;
260
- --sw: 220px; /* Reduced sidebar width */
261
- }
262
-
263
- *, *::before, *::after { box-sizing: border-box; }
264
-
265
- html, body {
266
- background: var(--bg) !important;
267
- font-family: var(--f) !important;
268
- color: var(--txt) !important;
269
- margin: 0; padding: 0;
270
- }
271
-
272
- .gradio-container {
273
- max-width: 100% !important;
274
- padding: 0 !important;
275
- margin: 0 !important;
276
- }
277
-
278
- #main-content {
279
- margin-left: var(--sw);
280
- width: calc(100% - var(--sw));
281
- padding: 0;
282
- min-height: 100vh;
283
- display: flex;
284
- flex-direction: column;
285
- }
286
-
287
- footer { display: none !important; }
288
-
289
- /* ── HERO BRAND ──────────────────────────────────────────────── */
290
- .hero-brand {
291
- display: flex;
292
- flex-direction: column;
293
- align-items: center;
294
- justify-content: center;
295
- padding: 40px 20px 20px;
296
- text-align: center;
297
- }
298
- .hero-logo-row {
299
- display: flex;
300
- align-items: center;
301
- gap: 12px;
302
- margin-bottom: 6px;
303
- }
304
- .hero-icon {
305
- width: 38px; height: 38px;
306
- background: linear-gradient(135deg, var(--acc), var(--acc2));
307
- border-radius: 12px;
308
- display: flex; align-items: center; justify-content: center;
309
- font-size: 20px; flex-shrink: 0;
310
- box-shadow: 0 4px 16px var(--aglow);
311
- color: #070c12;
312
- }
313
- .hero-name { font-size: 24px; font-weight: 800; color: #fff; letter-spacing: -.5px; }
314
- .hero-name em { font-style: normal; color: var(--acc); }
315
-
316
- /* ── FIXED SIDEBAR ───────────────────────────────────────────── */
317
- #anifind-sidebar {
318
- position: fixed;
319
- top: 0; left: 0;
320
- width: var(--sw);
321
- height: 100vh;
322
- background: var(--bg);
323
- border-right: 1px solid var(--bdr);
324
- display: flex;
325
- flex-direction: column;
326
- padding: 30px 20px;
327
- overflow-y: auto;
328
- z-index: 999;
329
- scrollbar-width: none;
330
- box-shadow: 4px 0 20px rgba(0,0,0,0.2);
331
- }
332
- #anifind-sidebar::-webkit-scrollbar { display: none; }
333
-
334
- .sb-tagline {
335
- font-size: 20px; font-weight: 800; color: #fff;
336
- line-height: 1.3; letter-spacing: -.3px; margin-bottom: 10px;
337
- }
338
- .sb-tagline em { font-style: normal; color: var(--acc); }
339
-
340
- .sb-sub {
341
- font-size: 13px; color: var(--mut);
342
- line-height: 1.5; margin-bottom: 30px;
343
- }
344
-
345
- .sb-sect {
346
- font-size: 11px; font-weight: 700;
347
- letter-spacing: 1.2px; text-transform: uppercase;
348
- color: var(--mut); margin-bottom: 12px;
349
- }
350
-
351
- .chips-col { display: flex; flex-direction: column; gap: 8px; }
352
-
353
- .chip {
354
- font-family: var(--f); font-size: 13px; font-weight: 600;
355
- color: var(--txt); background: var(--bg2);
356
- border: 1px solid var(--bdr);
357
- padding: 10px 14px; border-radius: 10px;
358
- cursor: pointer; text-align: left;
359
- transition: all .3s cubic-bezier(0.34, 1.56, 0.64, 1);
360
- }
361
- .chip:hover {
362
- color: var(--acc); background: var(--bg3);
363
- border-color: rgba(0,200,150,.5);
364
- transform: translateX(4px) scale(1.02);
365
- box-shadow: 0 4px 12px rgba(0,0,0,0.25);
366
- }
367
-
368
- /* ── STICKY SEARCH BAR ───────────────────────────────────────── */
369
- #search-zone {
370
- position: sticky !important;
371
- top: 0 !important; z-index: 100;
372
- background: rgba(7,12,18,0.85) !important;
373
- backdrop-filter: blur(16px);
374
- -webkit-backdrop-filter: blur(16px);
375
- border-bottom: 1px solid var(--bdr) !important;
376
- padding: 16px 24px !important;
377
- gap: 12px !important;
378
- align-items: center !important;
379
- }
380
-
381
- #query-input {
382
- background: var(--bg2) !important;
383
- border: 2px solid var(--bdr) !important;
384
- border-radius: 12px !important;
385
- transition: all .3s cubic-bezier(0.16, 1, 0.3, 1) !important;
386
- }
387
- #query-input:focus-within {
388
- border-color: var(--acc) !important;
389
- box-shadow: 0 0 0 3px var(--adim) !important;
390
- }
391
- #query-input textarea {
392
- font-family: var(--f) !important;
393
- font-size: 13px !important;
394
- padding: 10px !important;
395
- background: transparent !important;
396
- color: #fff !important;
397
- border: none !important;
398
- box-shadow: none !important;
399
- }
400
-
401
- #send-btn { min-width: unset !important; }
402
- #send-btn button {
403
- background: linear-gradient(135deg, var(--acc), #00ffc8) !important;
404
- color: #000 !important;
405
- border: none !important;
406
- border-radius: 12px !important;
407
- height: 42px !important;
408
- width: 42px !important;
409
- font-size: 18px !important;
410
- font-weight: 900 !important;
411
- cursor: pointer;
412
- transition: all .3s cubic-bezier(0.34, 1.56, 0.64, 1) !important;
413
- box-shadow: 0 4px 14px rgba(0, 200, 150, 0.4), inset 0 2px 0 rgba(255,255,255,0.3) !important;
414
- display: flex; align-items: center; justify-content: center;
415
- }
416
- #send-btn button:hover {
417
- transform: translateY(-2px) scale(1.05) !important;
418
- box-shadow: 0 8px 20px rgba(0, 200, 150, 0.6) !important;
419
- }
420
- #send-btn button:active { transform: scale(0.95) !important; }
421
-
422
- /* ── FILTER BAR ──────────────────────────────────────────────── */
423
- #filter-bar {
424
- background: var(--bg2) !important;
425
- border: 1px solid var(--bdr) !important;
426
- border-radius: 14px !important;
427
- margin: 16px 24px 0 !important;
428
- padding: 16px 24px !important;
429
- box-shadow: 0 4px 16px rgba(0,0,0,0.15) !important;
430
- gap: 16px !important;
431
- }
432
- #filter-bar label span {
433
- color: var(--mut) !important;
434
- font-weight: 700 !important;
435
- text-transform: uppercase !important;
436
- letter-spacing: 1px !important;
437
- font-size: 11px !important;
438
- }
439
-
440
- /* ── OUTPUT ZONE ─────────────────────────────────────────────── */
441
- #output-zone {
442
- padding: 24px !important;
443
- flex: 1;
444
- }
445
-
446
- .hud {
447
- display: flex; align-items: center; justify-content: space-between;
448
- background: var(--bg2); border: 1px solid var(--bdr);
449
- border-radius: 12px; padding: 12px 20px;
450
- margin-bottom: 24px;
451
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
452
- }
453
- .hud-pill {
454
- font-size: 13px; font-weight: 700;
455
- padding: 6px 14px; border-radius: 100px;
456
- }
457
- .hud-pill.ready { background: var(--adim); color: var(--acc); border: 1px solid rgba(0,200,150,.3); }
458
- .hud-pill.idle { background: rgba(251,191,36,.1); color: var(--amb); border: 1px solid rgba(251,191,36,.2); }
459
- .hud-pill.err { background: rgba(248,113,113,.1); color: var(--red); border: 1px solid rgba(248,113,113,.2); }
460
-
461
- .hud-stats { display: flex; align-items: center; gap: 16px; }
462
- .hud-stat { display: flex; flex-direction: column; align-items: flex-end; }
463
- .hud-n { font-size: 18px; font-weight: 800; color: #fff; line-height: 1; }
464
- .hud-l { font-size: 11px; font-weight: 600; color: var(--mut); text-transform: uppercase; margin-top: 4px; }
465
- .hud-sep { width: 1px; height: 28px; background: var(--bdr); }
466
-
467
- .main-layout {
468
- display: grid; grid-template-columns: 1fr 350px;
469
- gap: 24px; align-items: start;
470
- }
471
-
472
- .recs-heading {
473
- font-size: 12px; font-weight: 800; letter-spacing: 1.5px;
474
- text-transform: uppercase; color: var(--mut);
475
- margin-bottom: 18px; padding-bottom: 10px;
476
- border-bottom: 2px solid var(--bdr);
477
- }
478
-
479
- /* ── RECOMMENDATION BLOCK ────────────────────────────────────── */
480
- .rec-block {
481
- background: var(--bg2);
482
- border: 1px solid var(--bdr);
483
- border-radius: 16px; overflow: hidden; margin-bottom: 20px;
484
- opacity: 0; transform: translateY(15px);
485
- animation: up .4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
486
- transition: all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
487
- box-shadow: 0 4px 16px rgba(0,0,0,0.1);
488
- }
489
- .rec-block:hover {
490
- border-color: rgba(0,200,150,.4);
491
- box-shadow: 0 8px 30px rgba(0,200,150,.15);
492
- transform: translateY(-4px) scale(1.01);
493
- }
494
- @keyframes up { to { opacity: 1; transform: translateY(0); } }
495
-
496
- .sc {
497
- display: grid; grid-template-columns: 130px 1fr;
498
- text-decoration: none !important; color: inherit !important;
499
- }
500
-
501
- .sc-poster {
502
- position: relative; overflow: hidden;
503
- background: var(--bg3); min-height: 180px;
504
- }
505
- .sc-poster img {
506
- width: 100%; height: 100%; object-fit: cover;
507
- display: block; transition: transform .5s cubic-bezier(0.16, 1, 0.3, 1);
508
- }
509
- .sc:hover .sc-poster img { transform: scale(1.06); }
510
-
511
- .sc-badge {
512
- position: absolute; top: 10px; left: 10px;
513
- background: rgba(7,12,18,.85); backdrop-filter: blur(8px);
514
- font-size: 12px; font-weight: 800; padding: 4px 10px;
515
- border-radius: 100px; border: 1px solid rgba(255,255,255,.15);
516
- box-shadow: 0 2px 8px rgba(0,0,0,0.3);
517
- }
518
-
519
- .sc-body { padding: 16px; display: flex; flex-direction: column; gap: 8px; }
520
-
521
- .sc-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 2px; }
522
- .mc {
523
- font-size: 10px; font-weight: 700; letter-spacing: .5px;
524
- text-transform: uppercase; color: var(--acc);
525
- background: var(--adim); border: 1px solid rgba(0,200,150,.25);
526
- padding: 3px 8px; border-radius: 6px;
527
- }
528
-
529
- .sc-title { font-size: 17px; font-weight: 800; color: #fff; line-height: 1.2; letter-spacing: -.3px; }
530
- .sc-eng { font-size: 12px; color: #a0b2c6; font-weight: 600; font-style: italic; margin-top: -2px; }
531
- .sc-studio { font-size: 12px; color: #8a9bb0; font-weight: 600; }
532
- .sc-synopsis { font-size: 12.5px; color: #dde3f0; line-height: 1.6; flex: 1; margin-top: 6px; }
533
-
534
- .pill-row { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px; }
535
- .pill {
536
- font-size: 12px; font-weight: 600; color: var(--txt);
537
- background: var(--bg3); border: 1px solid var(--bdr);
538
- padding: 4px 12px; border-radius: 100px;
539
- }
540
-
541
- .mal-btn {
542
- display: inline-block; margin-top: 14px;
543
- font-size: 12px; font-weight: 700; color: var(--acc);
544
- background: var(--adim); border: 1px solid rgba(0,200,150,.3);
545
- padding: 8px 16px; border-radius: 8px; letter-spacing: .5px;
546
- text-transform: uppercase; align-self: flex-start;
547
- transition: all .2s ease;
548
- }
549
- .sc:hover .mal-btn {
550
- background: var(--acc); color: #000;
551
- transform: translateY(-2px);
552
- box-shadow: 0 4px 12px rgba(0,200,150,0.3);
553
- }
554
-
555
- .llm-pitch {
556
- padding: 14px 16px; font-size: 14px; color: #fff;
557
- line-height: 1.6; border-top: 1px solid var(--bdr);
558
- background: rgba(255,255,255,0.02);
559
- }
560
-
561
- /* ── RETRIEVED SIDEBAR ───────────────────────────────────────── */
562
- .sidebar {
563
- background: var(--bg2); border: 1px solid var(--bdr);
564
- border-radius: 16px; padding: 18px;
565
- position: sticky; top: 110px;
566
- max-height: calc(100vh - 130px);
567
- overflow-y: auto; scrollbar-width: thin;
568
- scrollbar-color: var(--mut) transparent;
569
- box-shadow: 0 4px 16px rgba(0,0,0,0.1);
570
- }
571
-
572
- .sidebar-head {
573
- display: flex; justify-content: space-between; align-items: center;
574
- margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid var(--bdr);
575
- }
576
- .sidebar-label {
577
- font-size: 11px; font-weight: 700; letter-spacing: 1px;
578
- text-transform: uppercase; color: var(--mut);
579
- }
580
- .sidebar-count {
581
- font-size: 12px; font-weight: 700; color: var(--acc);
582
- background: var(--adim); padding: 3px 10px; border-radius: 100px;
583
- }
584
-
585
- .rr-list { display: flex; flex-direction: column; gap: 4px; }
586
- .rr {
587
- display: grid; grid-template-columns: 22px 44px 1fr auto;
588
- gap: 12px; align-items: center; padding: 8px;
589
- border-radius: 10px; text-decoration: none !important;
590
- color: inherit !important; transition: all .2s;
591
- }
592
- .rr:hover {
593
- background: var(--bg3);
594
- transform: translateX(4px);
595
- }
596
-
597
- .rr-idx { font-size: 11px; font-weight: 700; color: var(--mut); text-align: center; }
598
- .rr-thumb {
599
- width: 44px; height: 62px; object-fit: cover;
600
- border-radius: 6px; background: var(--bg3); display: block;
601
- }
602
- .rr-info { display: flex; flex-direction: column; gap: 4px; overflow: hidden; }
603
- .rr-title {
604
- font-size: 13px; font-weight: 700; color: #fff;
605
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
606
- }
607
- .rr-sub {
608
- font-size: 11px; font-weight: 500; color: #8a9bb0;
609
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
610
- }
611
- .rr-bar-bg { height: 4px; background: var(--bg3); border-radius: 4px; overflow: hidden; margin-top: 2px; }
612
- .rr-bar-fill {
613
- height: 100%;
614
- background: linear-gradient(90deg, var(--acc), var(--acc2));
615
- border-radius: 4px;
616
- }
617
- .rr-right { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
618
- .rr-score { font-size: 12px; font-weight: 700; color: var(--amb); }
619
- .rr-pct { font-size: 11px; font-weight: 600; color: var(--mut); }
620
-
621
- /* ── RESPONSIVE DESIGN ───────────────────────────────────────── */
622
- @media (max-width: 1024px) {
623
- .main-layout { grid-template-columns: 1fr; }
624
- .sidebar { position: relative; top: 0; max-height: none; margin-top: 24px; }
625
- }
626
- @media (max-width: 860px) {
627
- #anifind-sidebar { width: 100%; position: relative; height: auto; border-right: none; border-bottom: 1px solid var(--bdr); }
628
- #main-content { margin-left: 0; width: 100%; }
629
- .sc { grid-template-columns: 1fr; }
630
- .sc-poster { min-height: 200px; }
631
- }
632
- """
633
-
634
- # =============================================================================
635
- # JAVASCRIPT (INJECTED VIA HEAD TO GUARANTEE EXECUTION)
636
- # =============================================================================
637
- JS = """
638
- <script>
639
- function initAniFindJS() {
640
- document.body.addEventListener('click', function(e) {
641
- var chip = e.target.closest('.chip');
642
- if (!chip) return;
643
- var q = chip.getAttribute('data-q');
644
- var ta = document.querySelector('#query-input textarea');
645
- if (ta && q) {
646
- // Svelte/React native value setter trigger
647
- var setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
648
- setter.call(ta, q);
649
- ta.dispatchEvent(new Event('input', { bubbles: true }));
650
-
651
- setTimeout(function() {
652
- var btn = document.querySelector('#send-btn button');
653
- if (btn) btn.click();
654
- }, 100);
655
- }
656
- });
657
-
658
- document.body.addEventListener('keydown', function(e) {
659
- if (e.target.tagName.toLowerCase() !== 'textarea') return;
660
- if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
661
- e.preventDefault();
662
- e.stopPropagation();
663
- var btn = document.querySelector('#send-btn button');
664
- if (btn) btn.click();
665
- }
666
- }, true);
667
- }
668
-
669
- // Ensure the script runs after DOM is ready
670
- if (document.readyState === "loading") {
671
- document.addEventListener("DOMContentLoaded", initAniFindJS);
672
- } else {
673
- initAniFindJS();
674
- }
675
- </script>
676
- """
677
-
678
- # =============================================================================
679
- # GRADIO INTERFACE
680
- # =============================================================================
681
-
682
-
683
- def create_interface():
684
- chips_html = "".join(
685
- f"<button class='chip' data-q='{s['query']}'>{s['label']}</button>"
686
- for s in SUGGESTIONS
687
- )
688
-
689
- sidebar_html = (
690
- "<div id='anifind-sidebar'>"
691
- "<p class='sb-tagline'>Find your next<br><em>favourite anime.</em></p>"
692
- "<p class='sb-sub'>Describe a vibe, a mood, a feeling — we'll match it to the perfect title.</p>"
693
- "<div class='sb-sect'>Quick searches</div>"
694
- f"<div class='chips-col'>{chips_html}</div>"
695
- "</div>"
696
- )
697
-
698
- hero_html = (
699
- "<div class='hero-brand'>"
700
- "<div class='hero-logo-row'>"
701
- "<div class='hero-icon'>&#x26E9;</div>"
702
- "<span class='hero-name'>Ani<em>Find</em></span>"
703
- "</div>"
704
- "</div>"
705
- )
706
-
707
- # Added `head=JS` to properly inject the event listeners globally
708
- with gr.Blocks(title="AniFind", head=JS) as demo:
709
-
710
- gr.HTML(sidebar_html)
711
-
712
- with gr.Column(elem_id="main-content"):
713
-
714
- gr.HTML(hero_html)
715
-
716
- with gr.Row(elem_id="search-zone"):
717
- query_input = gr.Textbox(
718
- label="", show_label=False, lines=2,
719
- placeholder="e.g. A melancholic sci-fi about memory and loss...",
720
- elem_id="query-input", scale=10,
721
- )
722
- submit_btn = gr.Button("➔", elem_id="send-btn", scale=1)
723
-
724
- with gr.Row(elem_id="filter-bar"):
725
- anime_type_dd = gr.Dropdown(
726
- choices=ANIME_TYPES, value="Any", label="Type", scale=1)
727
- genre_dd = gr.Dropdown(
728
- choices=GENRES, value=["Any"], label="Genre",
729
- multiselect=True, scale=2)
730
- score_slider = gr.Slider(
731
- minimum=0, maximum=10, value=0, step=0.5,
732
- label="Min Rating", scale=1)
733
- n_slider = gr.Slider(
734
- minimum=1, maximum=8, value=3, step=1,
735
- label="Results", scale=1)
736
-
737
- with gr.Column(elem_id="output-zone"):
738
- output_header = gr.HTML(
739
- value="<div class='hud'><div class='hud-pill idle'>Waiting for your prompt...</div></div>"
740
- )
741
- output_body = gr.HTML(value="")
742
-
743
- ins = [query_input, score_slider, genre_dd, anime_type_dd, n_slider]
744
- outs = [output_header, output_body]
745
- submit_btn.click(fn=get_recommendations, inputs=ins, outputs=outs)
746
- query_input.submit(fn=get_recommendations, inputs=ins, outputs=outs)
747
-
748
- return demo
749
-
750
-
751
- if __name__ == "__main__":
752
- demo = create_interface()
753
-
754
- theme = themes.Base(
755
- font=[themes.GoogleFont("Plus Jakarta Sans"),
756
- "ui-sans-serif", "sans-serif"],
757
- ).set(
758
- body_background_fill="transparent",
759
- block_background_fill="transparent",
760
- block_border_width="0px",
761
- input_background_fill="transparent",
762
- border_color_primary="transparent",
763
- shadow_drop="none",
764
- shadow_drop_lg="none",
765
- )
766
-
767
- demo.launch(
768
- server_name="0.0.0.0",
769
- server_port=7860,
770
- share=False,
771
- css=CSS,
772
- theme=theme,
773
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/lib/types.ts DELETED
@@ -1,40 +0,0 @@
1
- export interface AnimeImages {
2
- webp?: { image_url?: string };
3
- jpg?: { image_url?: string };
4
- }
5
-
6
- export interface Anime {
7
- title: string;
8
- title_english?: string;
9
- score?: number | string;
10
- scored_by?: number | string;
11
- year?: number | string;
12
- type?: string;
13
- episodes?: number | string;
14
- genres?: string[];
15
- studios?: string[];
16
- synopsis?: string;
17
- mal_url?: string;
18
- images?: AnimeImages;
19
- final_hybrid_score?: number;
20
- }
21
-
22
- export interface RecommendationResponse {
23
- query: string;
24
- recommendations: string;
25
- retrieved_count: number;
26
- retrieved_animes: Anime[];
27
- metadata: {
28
- model: string;
29
- retriever_k: number;
30
- "Time taken for LLM + vector search": string;
31
- };
32
- }
33
-
34
- export interface SearchFilters {
35
- query: string;
36
- n_results: number;
37
- min_score?: number;
38
- genre_filter?: string[];
39
- anime_type?: string;
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/next.config.mjs DELETED
@@ -1,3 +0,0 @@
1
- /** @type {import('next').NextConfig} */
2
- const nextConfig = {};
3
- export default nextConfig;
 
 
 
 
ui/package-lock.json DELETED
@@ -1,1929 +0,0 @@
1
- {
2
- "name": "anifind",
3
- "version": "0.1.0",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "": {
8
- "name": "anifind",
9
- "version": "0.1.0",
10
- "dependencies": {
11
- "next": "15.1.0",
12
- "react": "^19.0.0",
13
- "react-dom": "^19.0.0"
14
- },
15
- "devDependencies": {
16
- "@types/node": "^20",
17
- "@types/react": "^19",
18
- "@types/react-dom": "^19",
19
- "autoprefixer": "^10.0.1",
20
- "postcss": "^8",
21
- "tailwindcss": "^3.4.1",
22
- "typescript": "^5"
23
- }
24
- },
25
- "node_modules/@alloc/quick-lru": {
26
- "version": "5.2.0",
27
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
28
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
29
- "dev": true,
30
- "engines": {
31
- "node": ">=10"
32
- },
33
- "funding": {
34
- "url": "https://github.com/sponsors/sindresorhus"
35
- }
36
- },
37
- "node_modules/@emnapi/runtime": {
38
- "version": "1.8.1",
39
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
40
- "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
41
- "optional": true,
42
- "dependencies": {
43
- "tslib": "^2.4.0"
44
- }
45
- },
46
- "node_modules/@img/sharp-darwin-arm64": {
47
- "version": "0.33.5",
48
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
49
- "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
50
- "cpu": [
51
- "arm64"
52
- ],
53
- "optional": true,
54
- "os": [
55
- "darwin"
56
- ],
57
- "engines": {
58
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
59
- },
60
- "funding": {
61
- "url": "https://opencollective.com/libvips"
62
- },
63
- "optionalDependencies": {
64
- "@img/sharp-libvips-darwin-arm64": "1.0.4"
65
- }
66
- },
67
- "node_modules/@img/sharp-darwin-x64": {
68
- "version": "0.33.5",
69
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
70
- "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
71
- "cpu": [
72
- "x64"
73
- ],
74
- "optional": true,
75
- "os": [
76
- "darwin"
77
- ],
78
- "engines": {
79
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
80
- },
81
- "funding": {
82
- "url": "https://opencollective.com/libvips"
83
- },
84
- "optionalDependencies": {
85
- "@img/sharp-libvips-darwin-x64": "1.0.4"
86
- }
87
- },
88
- "node_modules/@img/sharp-libvips-darwin-arm64": {
89
- "version": "1.0.4",
90
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
91
- "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
92
- "cpu": [
93
- "arm64"
94
- ],
95
- "optional": true,
96
- "os": [
97
- "darwin"
98
- ],
99
- "funding": {
100
- "url": "https://opencollective.com/libvips"
101
- }
102
- },
103
- "node_modules/@img/sharp-libvips-darwin-x64": {
104
- "version": "1.0.4",
105
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
106
- "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
107
- "cpu": [
108
- "x64"
109
- ],
110
- "optional": true,
111
- "os": [
112
- "darwin"
113
- ],
114
- "funding": {
115
- "url": "https://opencollective.com/libvips"
116
- }
117
- },
118
- "node_modules/@img/sharp-libvips-linux-arm": {
119
- "version": "1.0.5",
120
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
121
- "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
122
- "cpu": [
123
- "arm"
124
- ],
125
- "optional": true,
126
- "os": [
127
- "linux"
128
- ],
129
- "funding": {
130
- "url": "https://opencollective.com/libvips"
131
- }
132
- },
133
- "node_modules/@img/sharp-libvips-linux-arm64": {
134
- "version": "1.0.4",
135
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
136
- "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
137
- "cpu": [
138
- "arm64"
139
- ],
140
- "optional": true,
141
- "os": [
142
- "linux"
143
- ],
144
- "funding": {
145
- "url": "https://opencollective.com/libvips"
146
- }
147
- },
148
- "node_modules/@img/sharp-libvips-linux-s390x": {
149
- "version": "1.0.4",
150
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
151
- "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
152
- "cpu": [
153
- "s390x"
154
- ],
155
- "optional": true,
156
- "os": [
157
- "linux"
158
- ],
159
- "funding": {
160
- "url": "https://opencollective.com/libvips"
161
- }
162
- },
163
- "node_modules/@img/sharp-libvips-linux-x64": {
164
- "version": "1.0.4",
165
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
166
- "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
167
- "cpu": [
168
- "x64"
169
- ],
170
- "optional": true,
171
- "os": [
172
- "linux"
173
- ],
174
- "funding": {
175
- "url": "https://opencollective.com/libvips"
176
- }
177
- },
178
- "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
179
- "version": "1.0.4",
180
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
181
- "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
182
- "cpu": [
183
- "arm64"
184
- ],
185
- "optional": true,
186
- "os": [
187
- "linux"
188
- ],
189
- "funding": {
190
- "url": "https://opencollective.com/libvips"
191
- }
192
- },
193
- "node_modules/@img/sharp-libvips-linuxmusl-x64": {
194
- "version": "1.0.4",
195
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
196
- "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
197
- "cpu": [
198
- "x64"
199
- ],
200
- "optional": true,
201
- "os": [
202
- "linux"
203
- ],
204
- "funding": {
205
- "url": "https://opencollective.com/libvips"
206
- }
207
- },
208
- "node_modules/@img/sharp-linux-arm": {
209
- "version": "0.33.5",
210
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
211
- "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
212
- "cpu": [
213
- "arm"
214
- ],
215
- "optional": true,
216
- "os": [
217
- "linux"
218
- ],
219
- "engines": {
220
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
221
- },
222
- "funding": {
223
- "url": "https://opencollective.com/libvips"
224
- },
225
- "optionalDependencies": {
226
- "@img/sharp-libvips-linux-arm": "1.0.5"
227
- }
228
- },
229
- "node_modules/@img/sharp-linux-arm64": {
230
- "version": "0.33.5",
231
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
232
- "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
233
- "cpu": [
234
- "arm64"
235
- ],
236
- "optional": true,
237
- "os": [
238
- "linux"
239
- ],
240
- "engines": {
241
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
242
- },
243
- "funding": {
244
- "url": "https://opencollective.com/libvips"
245
- },
246
- "optionalDependencies": {
247
- "@img/sharp-libvips-linux-arm64": "1.0.4"
248
- }
249
- },
250
- "node_modules/@img/sharp-linux-s390x": {
251
- "version": "0.33.5",
252
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
253
- "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
254
- "cpu": [
255
- "s390x"
256
- ],
257
- "optional": true,
258
- "os": [
259
- "linux"
260
- ],
261
- "engines": {
262
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
263
- },
264
- "funding": {
265
- "url": "https://opencollective.com/libvips"
266
- },
267
- "optionalDependencies": {
268
- "@img/sharp-libvips-linux-s390x": "1.0.4"
269
- }
270
- },
271
- "node_modules/@img/sharp-linux-x64": {
272
- "version": "0.33.5",
273
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
274
- "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
275
- "cpu": [
276
- "x64"
277
- ],
278
- "optional": true,
279
- "os": [
280
- "linux"
281
- ],
282
- "engines": {
283
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
284
- },
285
- "funding": {
286
- "url": "https://opencollective.com/libvips"
287
- },
288
- "optionalDependencies": {
289
- "@img/sharp-libvips-linux-x64": "1.0.4"
290
- }
291
- },
292
- "node_modules/@img/sharp-linuxmusl-arm64": {
293
- "version": "0.33.5",
294
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
295
- "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
296
- "cpu": [
297
- "arm64"
298
- ],
299
- "optional": true,
300
- "os": [
301
- "linux"
302
- ],
303
- "engines": {
304
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
305
- },
306
- "funding": {
307
- "url": "https://opencollective.com/libvips"
308
- },
309
- "optionalDependencies": {
310
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
311
- }
312
- },
313
- "node_modules/@img/sharp-linuxmusl-x64": {
314
- "version": "0.33.5",
315
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
316
- "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
317
- "cpu": [
318
- "x64"
319
- ],
320
- "optional": true,
321
- "os": [
322
- "linux"
323
- ],
324
- "engines": {
325
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
326
- },
327
- "funding": {
328
- "url": "https://opencollective.com/libvips"
329
- },
330
- "optionalDependencies": {
331
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
332
- }
333
- },
334
- "node_modules/@img/sharp-wasm32": {
335
- "version": "0.33.5",
336
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
337
- "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
338
- "cpu": [
339
- "wasm32"
340
- ],
341
- "optional": true,
342
- "dependencies": {
343
- "@emnapi/runtime": "^1.2.0"
344
- },
345
- "engines": {
346
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
347
- },
348
- "funding": {
349
- "url": "https://opencollective.com/libvips"
350
- }
351
- },
352
- "node_modules/@img/sharp-win32-ia32": {
353
- "version": "0.33.5",
354
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
355
- "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
356
- "cpu": [
357
- "ia32"
358
- ],
359
- "optional": true,
360
- "os": [
361
- "win32"
362
- ],
363
- "engines": {
364
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
365
- },
366
- "funding": {
367
- "url": "https://opencollective.com/libvips"
368
- }
369
- },
370
- "node_modules/@img/sharp-win32-x64": {
371
- "version": "0.33.5",
372
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
373
- "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
374
- "cpu": [
375
- "x64"
376
- ],
377
- "optional": true,
378
- "os": [
379
- "win32"
380
- ],
381
- "engines": {
382
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
383
- },
384
- "funding": {
385
- "url": "https://opencollective.com/libvips"
386
- }
387
- },
388
- "node_modules/@jridgewell/gen-mapping": {
389
- "version": "0.3.13",
390
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
391
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
392
- "dev": true,
393
- "dependencies": {
394
- "@jridgewell/sourcemap-codec": "^1.5.0",
395
- "@jridgewell/trace-mapping": "^0.3.24"
396
- }
397
- },
398
- "node_modules/@jridgewell/resolve-uri": {
399
- "version": "3.1.2",
400
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
401
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
402
- "dev": true,
403
- "engines": {
404
- "node": ">=6.0.0"
405
- }
406
- },
407
- "node_modules/@jridgewell/sourcemap-codec": {
408
- "version": "1.5.5",
409
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
410
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
411
- "dev": true
412
- },
413
- "node_modules/@jridgewell/trace-mapping": {
414
- "version": "0.3.31",
415
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
416
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
417
- "dev": true,
418
- "dependencies": {
419
- "@jridgewell/resolve-uri": "^3.1.0",
420
- "@jridgewell/sourcemap-codec": "^1.4.14"
421
- }
422
- },
423
- "node_modules/@next/env": {
424
- "version": "15.1.0",
425
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.0.tgz",
426
- "integrity": "sha512-UcCO481cROsqJuszPPXJnb7GGuLq617ve4xuAyyNG4VSSocJNtMU5Fsx+Lp6mlN8c7W58aZLc5y6D/2xNmaK+w=="
427
- },
428
- "node_modules/@next/swc-darwin-arm64": {
429
- "version": "15.1.0",
430
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.0.tgz",
431
- "integrity": "sha512-ZU8d7xxpX14uIaFC3nsr4L++5ZS/AkWDm1PzPO6gD9xWhFkOj2hzSbSIxoncsnlJXB1CbLOfGVN4Zk9tg83PUw==",
432
- "cpu": [
433
- "arm64"
434
- ],
435
- "optional": true,
436
- "os": [
437
- "darwin"
438
- ],
439
- "engines": {
440
- "node": ">= 10"
441
- }
442
- },
443
- "node_modules/@next/swc-darwin-x64": {
444
- "version": "15.1.0",
445
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.0.tgz",
446
- "integrity": "sha512-DQ3RiUoW2XC9FcSM4ffpfndq1EsLV0fj0/UY33i7eklW5akPUCo6OX2qkcLXZ3jyPdo4sf2flwAED3AAq3Om2Q==",
447
- "cpu": [
448
- "x64"
449
- ],
450
- "optional": true,
451
- "os": [
452
- "darwin"
453
- ],
454
- "engines": {
455
- "node": ">= 10"
456
- }
457
- },
458
- "node_modules/@next/swc-linux-arm64-gnu": {
459
- "version": "15.1.0",
460
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.0.tgz",
461
- "integrity": "sha512-M+vhTovRS2F//LMx9KtxbkWk627l5Q7AqXWWWrfIzNIaUFiz2/NkOFkxCFyNyGACi5YbA8aekzCLtbDyfF/v5Q==",
462
- "cpu": [
463
- "arm64"
464
- ],
465
- "optional": true,
466
- "os": [
467
- "linux"
468
- ],
469
- "engines": {
470
- "node": ">= 10"
471
- }
472
- },
473
- "node_modules/@next/swc-linux-arm64-musl": {
474
- "version": "15.1.0",
475
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.0.tgz",
476
- "integrity": "sha512-Qn6vOuwaTCx3pNwygpSGtdIu0TfS1KiaYLYXLH5zq1scoTXdwYfdZtwvJTpB1WrLgiQE2Ne2kt8MZok3HlFqmg==",
477
- "cpu": [
478
- "arm64"
479
- ],
480
- "optional": true,
481
- "os": [
482
- "linux"
483
- ],
484
- "engines": {
485
- "node": ">= 10"
486
- }
487
- },
488
- "node_modules/@next/swc-linux-x64-gnu": {
489
- "version": "15.1.0",
490
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.0.tgz",
491
- "integrity": "sha512-yeNh9ofMqzOZ5yTOk+2rwncBzucc6a1lyqtg8xZv0rH5znyjxHOWsoUtSq4cUTeeBIiXXX51QOOe+VoCjdXJRw==",
492
- "cpu": [
493
- "x64"
494
- ],
495
- "optional": true,
496
- "os": [
497
- "linux"
498
- ],
499
- "engines": {
500
- "node": ">= 10"
501
- }
502
- },
503
- "node_modules/@next/swc-linux-x64-musl": {
504
- "version": "15.1.0",
505
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.0.tgz",
506
- "integrity": "sha512-t9IfNkHQs/uKgPoyEtU912MG6a1j7Had37cSUyLTKx9MnUpjj+ZDKw9OyqTI9OwIIv0wmkr1pkZy+3T5pxhJPg==",
507
- "cpu": [
508
- "x64"
509
- ],
510
- "optional": true,
511
- "os": [
512
- "linux"
513
- ],
514
- "engines": {
515
- "node": ">= 10"
516
- }
517
- },
518
- "node_modules/@next/swc-win32-arm64-msvc": {
519
- "version": "15.1.0",
520
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.0.tgz",
521
- "integrity": "sha512-WEAoHyG14t5sTavZa1c6BnOIEukll9iqFRTavqRVPfYmfegOAd5MaZfXgOGG6kGo1RduyGdTHD4+YZQSdsNZXg==",
522
- "cpu": [
523
- "arm64"
524
- ],
525
- "optional": true,
526
- "os": [
527
- "win32"
528
- ],
529
- "engines": {
530
- "node": ">= 10"
531
- }
532
- },
533
- "node_modules/@next/swc-win32-x64-msvc": {
534
- "version": "15.1.0",
535
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.0.tgz",
536
- "integrity": "sha512-J1YdKuJv9xcixzXR24Dv+4SaDKc2jj31IVUEMdO5xJivMTXuE6MAdIi4qPjSymHuFG8O5wbfWKnhJUcHHpj5CA==",
537
- "cpu": [
538
- "x64"
539
- ],
540
- "optional": true,
541
- "os": [
542
- "win32"
543
- ],
544
- "engines": {
545
- "node": ">= 10"
546
- }
547
- },
548
- "node_modules/@nodelib/fs.scandir": {
549
- "version": "2.1.5",
550
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
551
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
552
- "dev": true,
553
- "dependencies": {
554
- "@nodelib/fs.stat": "2.0.5",
555
- "run-parallel": "^1.1.9"
556
- },
557
- "engines": {
558
- "node": ">= 8"
559
- }
560
- },
561
- "node_modules/@nodelib/fs.stat": {
562
- "version": "2.0.5",
563
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
564
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
565
- "dev": true,
566
- "engines": {
567
- "node": ">= 8"
568
- }
569
- },
570
- "node_modules/@nodelib/fs.walk": {
571
- "version": "1.2.8",
572
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
573
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
574
- "dev": true,
575
- "dependencies": {
576
- "@nodelib/fs.scandir": "2.1.5",
577
- "fastq": "^1.6.0"
578
- },
579
- "engines": {
580
- "node": ">= 8"
581
- }
582
- },
583
- "node_modules/@swc/counter": {
584
- "version": "0.1.3",
585
- "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
586
- "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
587
- },
588
- "node_modules/@swc/helpers": {
589
- "version": "0.5.15",
590
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
591
- "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
592
- "dependencies": {
593
- "tslib": "^2.8.0"
594
- }
595
- },
596
- "node_modules/@types/node": {
597
- "version": "20.19.37",
598
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
599
- "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
600
- "dev": true,
601
- "dependencies": {
602
- "undici-types": "~6.21.0"
603
- }
604
- },
605
- "node_modules/@types/react": {
606
- "version": "19.2.14",
607
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
608
- "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
609
- "dev": true,
610
- "dependencies": {
611
- "csstype": "^3.2.2"
612
- }
613
- },
614
- "node_modules/@types/react-dom": {
615
- "version": "19.2.3",
616
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
617
- "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
618
- "dev": true,
619
- "peerDependencies": {
620
- "@types/react": "^19.2.0"
621
- }
622
- },
623
- "node_modules/any-promise": {
624
- "version": "1.3.0",
625
- "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
626
- "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
627
- "dev": true
628
- },
629
- "node_modules/anymatch": {
630
- "version": "3.1.3",
631
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
632
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
633
- "dev": true,
634
- "dependencies": {
635
- "normalize-path": "^3.0.0",
636
- "picomatch": "^2.0.4"
637
- },
638
- "engines": {
639
- "node": ">= 8"
640
- }
641
- },
642
- "node_modules/arg": {
643
- "version": "5.0.2",
644
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
645
- "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
646
- "dev": true
647
- },
648
- "node_modules/autoprefixer": {
649
- "version": "10.4.27",
650
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz",
651
- "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==",
652
- "dev": true,
653
- "funding": [
654
- {
655
- "type": "opencollective",
656
- "url": "https://opencollective.com/postcss/"
657
- },
658
- {
659
- "type": "tidelift",
660
- "url": "https://tidelift.com/funding/github/npm/autoprefixer"
661
- },
662
- {
663
- "type": "github",
664
- "url": "https://github.com/sponsors/ai"
665
- }
666
- ],
667
- "dependencies": {
668
- "browserslist": "^4.28.1",
669
- "caniuse-lite": "^1.0.30001774",
670
- "fraction.js": "^5.3.4",
671
- "picocolors": "^1.1.1",
672
- "postcss-value-parser": "^4.2.0"
673
- },
674
- "bin": {
675
- "autoprefixer": "bin/autoprefixer"
676
- },
677
- "engines": {
678
- "node": "^10 || ^12 || >=14"
679
- },
680
- "peerDependencies": {
681
- "postcss": "^8.1.0"
682
- }
683
- },
684
- "node_modules/baseline-browser-mapping": {
685
- "version": "2.10.0",
686
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
687
- "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
688
- "dev": true,
689
- "bin": {
690
- "baseline-browser-mapping": "dist/cli.cjs"
691
- },
692
- "engines": {
693
- "node": ">=6.0.0"
694
- }
695
- },
696
- "node_modules/binary-extensions": {
697
- "version": "2.3.0",
698
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
699
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
700
- "dev": true,
701
- "engines": {
702
- "node": ">=8"
703
- },
704
- "funding": {
705
- "url": "https://github.com/sponsors/sindresorhus"
706
- }
707
- },
708
- "node_modules/braces": {
709
- "version": "3.0.3",
710
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
711
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
712
- "dev": true,
713
- "dependencies": {
714
- "fill-range": "^7.1.1"
715
- },
716
- "engines": {
717
- "node": ">=8"
718
- }
719
- },
720
- "node_modules/browserslist": {
721
- "version": "4.28.1",
722
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
723
- "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
724
- "dev": true,
725
- "funding": [
726
- {
727
- "type": "opencollective",
728
- "url": "https://opencollective.com/browserslist"
729
- },
730
- {
731
- "type": "tidelift",
732
- "url": "https://tidelift.com/funding/github/npm/browserslist"
733
- },
734
- {
735
- "type": "github",
736
- "url": "https://github.com/sponsors/ai"
737
- }
738
- ],
739
- "dependencies": {
740
- "baseline-browser-mapping": "^2.9.0",
741
- "caniuse-lite": "^1.0.30001759",
742
- "electron-to-chromium": "^1.5.263",
743
- "node-releases": "^2.0.27",
744
- "update-browserslist-db": "^1.2.0"
745
- },
746
- "bin": {
747
- "browserslist": "cli.js"
748
- },
749
- "engines": {
750
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
751
- }
752
- },
753
- "node_modules/busboy": {
754
- "version": "1.6.0",
755
- "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
756
- "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
757
- "dependencies": {
758
- "streamsearch": "^1.1.0"
759
- },
760
- "engines": {
761
- "node": ">=10.16.0"
762
- }
763
- },
764
- "node_modules/camelcase-css": {
765
- "version": "2.0.1",
766
- "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
767
- "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
768
- "dev": true,
769
- "engines": {
770
- "node": ">= 6"
771
- }
772
- },
773
- "node_modules/caniuse-lite": {
774
- "version": "1.0.30001777",
775
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
776
- "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==",
777
- "funding": [
778
- {
779
- "type": "opencollective",
780
- "url": "https://opencollective.com/browserslist"
781
- },
782
- {
783
- "type": "tidelift",
784
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
785
- },
786
- {
787
- "type": "github",
788
- "url": "https://github.com/sponsors/ai"
789
- }
790
- ]
791
- },
792
- "node_modules/chokidar": {
793
- "version": "3.6.0",
794
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
795
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
796
- "dev": true,
797
- "dependencies": {
798
- "anymatch": "~3.1.2",
799
- "braces": "~3.0.2",
800
- "glob-parent": "~5.1.2",
801
- "is-binary-path": "~2.1.0",
802
- "is-glob": "~4.0.1",
803
- "normalize-path": "~3.0.0",
804
- "readdirp": "~3.6.0"
805
- },
806
- "engines": {
807
- "node": ">= 8.10.0"
808
- },
809
- "funding": {
810
- "url": "https://paulmillr.com/funding/"
811
- },
812
- "optionalDependencies": {
813
- "fsevents": "~2.3.2"
814
- }
815
- },
816
- "node_modules/chokidar/node_modules/glob-parent": {
817
- "version": "5.1.2",
818
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
819
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
820
- "dev": true,
821
- "dependencies": {
822
- "is-glob": "^4.0.1"
823
- },
824
- "engines": {
825
- "node": ">= 6"
826
- }
827
- },
828
- "node_modules/client-only": {
829
- "version": "0.0.1",
830
- "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
831
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
832
- },
833
- "node_modules/color": {
834
- "version": "4.2.3",
835
- "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
836
- "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
837
- "optional": true,
838
- "dependencies": {
839
- "color-convert": "^2.0.1",
840
- "color-string": "^1.9.0"
841
- },
842
- "engines": {
843
- "node": ">=12.5.0"
844
- }
845
- },
846
- "node_modules/color-convert": {
847
- "version": "2.0.1",
848
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
849
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
850
- "optional": true,
851
- "dependencies": {
852
- "color-name": "~1.1.4"
853
- },
854
- "engines": {
855
- "node": ">=7.0.0"
856
- }
857
- },
858
- "node_modules/color-name": {
859
- "version": "1.1.4",
860
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
861
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
862
- "optional": true
863
- },
864
- "node_modules/color-string": {
865
- "version": "1.9.1",
866
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
867
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
868
- "optional": true,
869
- "dependencies": {
870
- "color-name": "^1.0.0",
871
- "simple-swizzle": "^0.2.2"
872
- }
873
- },
874
- "node_modules/commander": {
875
- "version": "4.1.1",
876
- "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
877
- "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
878
- "dev": true,
879
- "engines": {
880
- "node": ">= 6"
881
- }
882
- },
883
- "node_modules/cssesc": {
884
- "version": "3.0.0",
885
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
886
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
887
- "dev": true,
888
- "bin": {
889
- "cssesc": "bin/cssesc"
890
- },
891
- "engines": {
892
- "node": ">=4"
893
- }
894
- },
895
- "node_modules/csstype": {
896
- "version": "3.2.3",
897
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
898
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
899
- "dev": true
900
- },
901
- "node_modules/detect-libc": {
902
- "version": "2.1.2",
903
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
904
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
905
- "optional": true,
906
- "engines": {
907
- "node": ">=8"
908
- }
909
- },
910
- "node_modules/didyoumean": {
911
- "version": "1.2.2",
912
- "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
913
- "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
914
- "dev": true
915
- },
916
- "node_modules/dlv": {
917
- "version": "1.1.3",
918
- "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
919
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
920
- "dev": true
921
- },
922
- "node_modules/electron-to-chromium": {
923
- "version": "1.5.307",
924
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz",
925
- "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==",
926
- "dev": true
927
- },
928
- "node_modules/escalade": {
929
- "version": "3.2.0",
930
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
931
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
932
- "dev": true,
933
- "engines": {
934
- "node": ">=6"
935
- }
936
- },
937
- "node_modules/fast-glob": {
938
- "version": "3.3.3",
939
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
940
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
941
- "dev": true,
942
- "dependencies": {
943
- "@nodelib/fs.stat": "^2.0.2",
944
- "@nodelib/fs.walk": "^1.2.3",
945
- "glob-parent": "^5.1.2",
946
- "merge2": "^1.3.0",
947
- "micromatch": "^4.0.8"
948
- },
949
- "engines": {
950
- "node": ">=8.6.0"
951
- }
952
- },
953
- "node_modules/fast-glob/node_modules/glob-parent": {
954
- "version": "5.1.2",
955
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
956
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
957
- "dev": true,
958
- "dependencies": {
959
- "is-glob": "^4.0.1"
960
- },
961
- "engines": {
962
- "node": ">= 6"
963
- }
964
- },
965
- "node_modules/fastq": {
966
- "version": "1.20.1",
967
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
968
- "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
969
- "dev": true,
970
- "dependencies": {
971
- "reusify": "^1.0.4"
972
- }
973
- },
974
- "node_modules/fill-range": {
975
- "version": "7.1.1",
976
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
977
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
978
- "dev": true,
979
- "dependencies": {
980
- "to-regex-range": "^5.0.1"
981
- },
982
- "engines": {
983
- "node": ">=8"
984
- }
985
- },
986
- "node_modules/fraction.js": {
987
- "version": "5.3.4",
988
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
989
- "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
990
- "dev": true,
991
- "engines": {
992
- "node": "*"
993
- },
994
- "funding": {
995
- "type": "github",
996
- "url": "https://github.com/sponsors/rawify"
997
- }
998
- },
999
- "node_modules/fsevents": {
1000
- "version": "2.3.3",
1001
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1002
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1003
- "dev": true,
1004
- "hasInstallScript": true,
1005
- "optional": true,
1006
- "os": [
1007
- "darwin"
1008
- ],
1009
- "engines": {
1010
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1011
- }
1012
- },
1013
- "node_modules/function-bind": {
1014
- "version": "1.1.2",
1015
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1016
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1017
- "dev": true,
1018
- "funding": {
1019
- "url": "https://github.com/sponsors/ljharb"
1020
- }
1021
- },
1022
- "node_modules/glob-parent": {
1023
- "version": "6.0.2",
1024
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
1025
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
1026
- "dev": true,
1027
- "dependencies": {
1028
- "is-glob": "^4.0.3"
1029
- },
1030
- "engines": {
1031
- "node": ">=10.13.0"
1032
- }
1033
- },
1034
- "node_modules/hasown": {
1035
- "version": "2.0.2",
1036
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
1037
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
1038
- "dev": true,
1039
- "dependencies": {
1040
- "function-bind": "^1.1.2"
1041
- },
1042
- "engines": {
1043
- "node": ">= 0.4"
1044
- }
1045
- },
1046
- "node_modules/is-arrayish": {
1047
- "version": "0.3.4",
1048
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
1049
- "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
1050
- "optional": true
1051
- },
1052
- "node_modules/is-binary-path": {
1053
- "version": "2.1.0",
1054
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
1055
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
1056
- "dev": true,
1057
- "dependencies": {
1058
- "binary-extensions": "^2.0.0"
1059
- },
1060
- "engines": {
1061
- "node": ">=8"
1062
- }
1063
- },
1064
- "node_modules/is-core-module": {
1065
- "version": "2.16.1",
1066
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
1067
- "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
1068
- "dev": true,
1069
- "dependencies": {
1070
- "hasown": "^2.0.2"
1071
- },
1072
- "engines": {
1073
- "node": ">= 0.4"
1074
- },
1075
- "funding": {
1076
- "url": "https://github.com/sponsors/ljharb"
1077
- }
1078
- },
1079
- "node_modules/is-extglob": {
1080
- "version": "2.1.1",
1081
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
1082
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
1083
- "dev": true,
1084
- "engines": {
1085
- "node": ">=0.10.0"
1086
- }
1087
- },
1088
- "node_modules/is-glob": {
1089
- "version": "4.0.3",
1090
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
1091
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
1092
- "dev": true,
1093
- "dependencies": {
1094
- "is-extglob": "^2.1.1"
1095
- },
1096
- "engines": {
1097
- "node": ">=0.10.0"
1098
- }
1099
- },
1100
- "node_modules/is-number": {
1101
- "version": "7.0.0",
1102
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
1103
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
1104
- "dev": true,
1105
- "engines": {
1106
- "node": ">=0.12.0"
1107
- }
1108
- },
1109
- "node_modules/jiti": {
1110
- "version": "1.21.7",
1111
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
1112
- "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
1113
- "dev": true,
1114
- "bin": {
1115
- "jiti": "bin/jiti.js"
1116
- }
1117
- },
1118
- "node_modules/lilconfig": {
1119
- "version": "3.1.3",
1120
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
1121
- "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
1122
- "dev": true,
1123
- "engines": {
1124
- "node": ">=14"
1125
- },
1126
- "funding": {
1127
- "url": "https://github.com/sponsors/antonk52"
1128
- }
1129
- },
1130
- "node_modules/lines-and-columns": {
1131
- "version": "1.2.4",
1132
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
1133
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
1134
- "dev": true
1135
- },
1136
- "node_modules/merge2": {
1137
- "version": "1.4.1",
1138
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
1139
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
1140
- "dev": true,
1141
- "engines": {
1142
- "node": ">= 8"
1143
- }
1144
- },
1145
- "node_modules/micromatch": {
1146
- "version": "4.0.8",
1147
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
1148
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
1149
- "dev": true,
1150
- "dependencies": {
1151
- "braces": "^3.0.3",
1152
- "picomatch": "^2.3.1"
1153
- },
1154
- "engines": {
1155
- "node": ">=8.6"
1156
- }
1157
- },
1158
- "node_modules/mz": {
1159
- "version": "2.7.0",
1160
- "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
1161
- "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
1162
- "dev": true,
1163
- "dependencies": {
1164
- "any-promise": "^1.0.0",
1165
- "object-assign": "^4.0.1",
1166
- "thenify-all": "^1.0.0"
1167
- }
1168
- },
1169
- "node_modules/nanoid": {
1170
- "version": "3.3.11",
1171
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
1172
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
1173
- "funding": [
1174
- {
1175
- "type": "github",
1176
- "url": "https://github.com/sponsors/ai"
1177
- }
1178
- ],
1179
- "bin": {
1180
- "nanoid": "bin/nanoid.cjs"
1181
- },
1182
- "engines": {
1183
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1184
- }
1185
- },
1186
- "node_modules/next": {
1187
- "version": "15.1.0",
1188
- "resolved": "https://registry.npmjs.org/next/-/next-15.1.0.tgz",
1189
- "integrity": "sha512-QKhzt6Y8rgLNlj30izdMbxAwjHMFANnLwDwZ+WQh5sMhyt4lEBqDK9QpvWHtIM4rINKPoJ8aiRZKg5ULSybVHw==",
1190
- "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.",
1191
- "dependencies": {
1192
- "@next/env": "15.1.0",
1193
- "@swc/counter": "0.1.3",
1194
- "@swc/helpers": "0.5.15",
1195
- "busboy": "1.6.0",
1196
- "caniuse-lite": "^1.0.30001579",
1197
- "postcss": "8.4.31",
1198
- "styled-jsx": "5.1.6"
1199
- },
1200
- "bin": {
1201
- "next": "dist/bin/next"
1202
- },
1203
- "engines": {
1204
- "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
1205
- },
1206
- "optionalDependencies": {
1207
- "@next/swc-darwin-arm64": "15.1.0",
1208
- "@next/swc-darwin-x64": "15.1.0",
1209
- "@next/swc-linux-arm64-gnu": "15.1.0",
1210
- "@next/swc-linux-arm64-musl": "15.1.0",
1211
- "@next/swc-linux-x64-gnu": "15.1.0",
1212
- "@next/swc-linux-x64-musl": "15.1.0",
1213
- "@next/swc-win32-arm64-msvc": "15.1.0",
1214
- "@next/swc-win32-x64-msvc": "15.1.0",
1215
- "sharp": "^0.33.5"
1216
- },
1217
- "peerDependencies": {
1218
- "@opentelemetry/api": "^1.1.0",
1219
- "@playwright/test": "^1.41.2",
1220
- "babel-plugin-react-compiler": "*",
1221
- "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
1222
- "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
1223
- "sass": "^1.3.0"
1224
- },
1225
- "peerDependenciesMeta": {
1226
- "@opentelemetry/api": {
1227
- "optional": true
1228
- },
1229
- "@playwright/test": {
1230
- "optional": true
1231
- },
1232
- "babel-plugin-react-compiler": {
1233
- "optional": true
1234
- },
1235
- "sass": {
1236
- "optional": true
1237
- }
1238
- }
1239
- },
1240
- "node_modules/next/node_modules/postcss": {
1241
- "version": "8.4.31",
1242
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
1243
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
1244
- "funding": [
1245
- {
1246
- "type": "opencollective",
1247
- "url": "https://opencollective.com/postcss/"
1248
- },
1249
- {
1250
- "type": "tidelift",
1251
- "url": "https://tidelift.com/funding/github/npm/postcss"
1252
- },
1253
- {
1254
- "type": "github",
1255
- "url": "https://github.com/sponsors/ai"
1256
- }
1257
- ],
1258
- "dependencies": {
1259
- "nanoid": "^3.3.6",
1260
- "picocolors": "^1.0.0",
1261
- "source-map-js": "^1.0.2"
1262
- },
1263
- "engines": {
1264
- "node": "^10 || ^12 || >=14"
1265
- }
1266
- },
1267
- "node_modules/node-releases": {
1268
- "version": "2.0.36",
1269
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
1270
- "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
1271
- "dev": true
1272
- },
1273
- "node_modules/normalize-path": {
1274
- "version": "3.0.0",
1275
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1276
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1277
- "dev": true,
1278
- "engines": {
1279
- "node": ">=0.10.0"
1280
- }
1281
- },
1282
- "node_modules/object-assign": {
1283
- "version": "4.1.1",
1284
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1285
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1286
- "dev": true,
1287
- "engines": {
1288
- "node": ">=0.10.0"
1289
- }
1290
- },
1291
- "node_modules/object-hash": {
1292
- "version": "3.0.0",
1293
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
1294
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
1295
- "dev": true,
1296
- "engines": {
1297
- "node": ">= 6"
1298
- }
1299
- },
1300
- "node_modules/path-parse": {
1301
- "version": "1.0.7",
1302
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
1303
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
1304
- "dev": true
1305
- },
1306
- "node_modules/picocolors": {
1307
- "version": "1.1.1",
1308
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1309
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
1310
- },
1311
- "node_modules/picomatch": {
1312
- "version": "2.3.1",
1313
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1314
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1315
- "dev": true,
1316
- "engines": {
1317
- "node": ">=8.6"
1318
- },
1319
- "funding": {
1320
- "url": "https://github.com/sponsors/jonschlinkert"
1321
- }
1322
- },
1323
- "node_modules/pify": {
1324
- "version": "2.3.0",
1325
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
1326
- "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
1327
- "dev": true,
1328
- "engines": {
1329
- "node": ">=0.10.0"
1330
- }
1331
- },
1332
- "node_modules/pirates": {
1333
- "version": "4.0.7",
1334
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
1335
- "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
1336
- "dev": true,
1337
- "engines": {
1338
- "node": ">= 6"
1339
- }
1340
- },
1341
- "node_modules/postcss": {
1342
- "version": "8.5.8",
1343
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
1344
- "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
1345
- "dev": true,
1346
- "funding": [
1347
- {
1348
- "type": "opencollective",
1349
- "url": "https://opencollective.com/postcss/"
1350
- },
1351
- {
1352
- "type": "tidelift",
1353
- "url": "https://tidelift.com/funding/github/npm/postcss"
1354
- },
1355
- {
1356
- "type": "github",
1357
- "url": "https://github.com/sponsors/ai"
1358
- }
1359
- ],
1360
- "dependencies": {
1361
- "nanoid": "^3.3.11",
1362
- "picocolors": "^1.1.1",
1363
- "source-map-js": "^1.2.1"
1364
- },
1365
- "engines": {
1366
- "node": "^10 || ^12 || >=14"
1367
- }
1368
- },
1369
- "node_modules/postcss-import": {
1370
- "version": "15.1.0",
1371
- "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
1372
- "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
1373
- "dev": true,
1374
- "dependencies": {
1375
- "postcss-value-parser": "^4.0.0",
1376
- "read-cache": "^1.0.0",
1377
- "resolve": "^1.1.7"
1378
- },
1379
- "engines": {
1380
- "node": ">=14.0.0"
1381
- },
1382
- "peerDependencies": {
1383
- "postcss": "^8.0.0"
1384
- }
1385
- },
1386
- "node_modules/postcss-js": {
1387
- "version": "4.1.0",
1388
- "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
1389
- "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
1390
- "dev": true,
1391
- "funding": [
1392
- {
1393
- "type": "opencollective",
1394
- "url": "https://opencollective.com/postcss/"
1395
- },
1396
- {
1397
- "type": "github",
1398
- "url": "https://github.com/sponsors/ai"
1399
- }
1400
- ],
1401
- "dependencies": {
1402
- "camelcase-css": "^2.0.1"
1403
- },
1404
- "engines": {
1405
- "node": "^12 || ^14 || >= 16"
1406
- },
1407
- "peerDependencies": {
1408
- "postcss": "^8.4.21"
1409
- }
1410
- },
1411
- "node_modules/postcss-load-config": {
1412
- "version": "6.0.1",
1413
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
1414
- "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
1415
- "dev": true,
1416
- "funding": [
1417
- {
1418
- "type": "opencollective",
1419
- "url": "https://opencollective.com/postcss/"
1420
- },
1421
- {
1422
- "type": "github",
1423
- "url": "https://github.com/sponsors/ai"
1424
- }
1425
- ],
1426
- "dependencies": {
1427
- "lilconfig": "^3.1.1"
1428
- },
1429
- "engines": {
1430
- "node": ">= 18"
1431
- },
1432
- "peerDependencies": {
1433
- "jiti": ">=1.21.0",
1434
- "postcss": ">=8.0.9",
1435
- "tsx": "^4.8.1",
1436
- "yaml": "^2.4.2"
1437
- },
1438
- "peerDependenciesMeta": {
1439
- "jiti": {
1440
- "optional": true
1441
- },
1442
- "postcss": {
1443
- "optional": true
1444
- },
1445
- "tsx": {
1446
- "optional": true
1447
- },
1448
- "yaml": {
1449
- "optional": true
1450
- }
1451
- }
1452
- },
1453
- "node_modules/postcss-nested": {
1454
- "version": "6.2.0",
1455
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
1456
- "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
1457
- "dev": true,
1458
- "funding": [
1459
- {
1460
- "type": "opencollective",
1461
- "url": "https://opencollective.com/postcss/"
1462
- },
1463
- {
1464
- "type": "github",
1465
- "url": "https://github.com/sponsors/ai"
1466
- }
1467
- ],
1468
- "dependencies": {
1469
- "postcss-selector-parser": "^6.1.1"
1470
- },
1471
- "engines": {
1472
- "node": ">=12.0"
1473
- },
1474
- "peerDependencies": {
1475
- "postcss": "^8.2.14"
1476
- }
1477
- },
1478
- "node_modules/postcss-selector-parser": {
1479
- "version": "6.1.2",
1480
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
1481
- "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
1482
- "dev": true,
1483
- "dependencies": {
1484
- "cssesc": "^3.0.0",
1485
- "util-deprecate": "^1.0.2"
1486
- },
1487
- "engines": {
1488
- "node": ">=4"
1489
- }
1490
- },
1491
- "node_modules/postcss-value-parser": {
1492
- "version": "4.2.0",
1493
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
1494
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
1495
- "dev": true
1496
- },
1497
- "node_modules/queue-microtask": {
1498
- "version": "1.2.3",
1499
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
1500
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
1501
- "dev": true,
1502
- "funding": [
1503
- {
1504
- "type": "github",
1505
- "url": "https://github.com/sponsors/feross"
1506
- },
1507
- {
1508
- "type": "patreon",
1509
- "url": "https://www.patreon.com/feross"
1510
- },
1511
- {
1512
- "type": "consulting",
1513
- "url": "https://feross.org/support"
1514
- }
1515
- ]
1516
- },
1517
- "node_modules/react": {
1518
- "version": "19.2.4",
1519
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
1520
- "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
1521
- "engines": {
1522
- "node": ">=0.10.0"
1523
- }
1524
- },
1525
- "node_modules/react-dom": {
1526
- "version": "19.2.4",
1527
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
1528
- "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
1529
- "dependencies": {
1530
- "scheduler": "^0.27.0"
1531
- },
1532
- "peerDependencies": {
1533
- "react": "^19.2.4"
1534
- }
1535
- },
1536
- "node_modules/read-cache": {
1537
- "version": "1.0.0",
1538
- "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
1539
- "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
1540
- "dev": true,
1541
- "dependencies": {
1542
- "pify": "^2.3.0"
1543
- }
1544
- },
1545
- "node_modules/readdirp": {
1546
- "version": "3.6.0",
1547
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1548
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1549
- "dev": true,
1550
- "dependencies": {
1551
- "picomatch": "^2.2.1"
1552
- },
1553
- "engines": {
1554
- "node": ">=8.10.0"
1555
- }
1556
- },
1557
- "node_modules/resolve": {
1558
- "version": "1.22.11",
1559
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
1560
- "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
1561
- "dev": true,
1562
- "dependencies": {
1563
- "is-core-module": "^2.16.1",
1564
- "path-parse": "^1.0.7",
1565
- "supports-preserve-symlinks-flag": "^1.0.0"
1566
- },
1567
- "bin": {
1568
- "resolve": "bin/resolve"
1569
- },
1570
- "engines": {
1571
- "node": ">= 0.4"
1572
- },
1573
- "funding": {
1574
- "url": "https://github.com/sponsors/ljharb"
1575
- }
1576
- },
1577
- "node_modules/reusify": {
1578
- "version": "1.1.0",
1579
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
1580
- "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
1581
- "dev": true,
1582
- "engines": {
1583
- "iojs": ">=1.0.0",
1584
- "node": ">=0.10.0"
1585
- }
1586
- },
1587
- "node_modules/run-parallel": {
1588
- "version": "1.2.0",
1589
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
1590
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
1591
- "dev": true,
1592
- "funding": [
1593
- {
1594
- "type": "github",
1595
- "url": "https://github.com/sponsors/feross"
1596
- },
1597
- {
1598
- "type": "patreon",
1599
- "url": "https://www.patreon.com/feross"
1600
- },
1601
- {
1602
- "type": "consulting",
1603
- "url": "https://feross.org/support"
1604
- }
1605
- ],
1606
- "dependencies": {
1607
- "queue-microtask": "^1.2.2"
1608
- }
1609
- },
1610
- "node_modules/scheduler": {
1611
- "version": "0.27.0",
1612
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
1613
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="
1614
- },
1615
- "node_modules/semver": {
1616
- "version": "7.7.4",
1617
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
1618
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
1619
- "optional": true,
1620
- "bin": {
1621
- "semver": "bin/semver.js"
1622
- },
1623
- "engines": {
1624
- "node": ">=10"
1625
- }
1626
- },
1627
- "node_modules/sharp": {
1628
- "version": "0.33.5",
1629
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
1630
- "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
1631
- "hasInstallScript": true,
1632
- "optional": true,
1633
- "dependencies": {
1634
- "color": "^4.2.3",
1635
- "detect-libc": "^2.0.3",
1636
- "semver": "^7.6.3"
1637
- },
1638
- "engines": {
1639
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
1640
- },
1641
- "funding": {
1642
- "url": "https://opencollective.com/libvips"
1643
- },
1644
- "optionalDependencies": {
1645
- "@img/sharp-darwin-arm64": "0.33.5",
1646
- "@img/sharp-darwin-x64": "0.33.5",
1647
- "@img/sharp-libvips-darwin-arm64": "1.0.4",
1648
- "@img/sharp-libvips-darwin-x64": "1.0.4",
1649
- "@img/sharp-libvips-linux-arm": "1.0.5",
1650
- "@img/sharp-libvips-linux-arm64": "1.0.4",
1651
- "@img/sharp-libvips-linux-s390x": "1.0.4",
1652
- "@img/sharp-libvips-linux-x64": "1.0.4",
1653
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
1654
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
1655
- "@img/sharp-linux-arm": "0.33.5",
1656
- "@img/sharp-linux-arm64": "0.33.5",
1657
- "@img/sharp-linux-s390x": "0.33.5",
1658
- "@img/sharp-linux-x64": "0.33.5",
1659
- "@img/sharp-linuxmusl-arm64": "0.33.5",
1660
- "@img/sharp-linuxmusl-x64": "0.33.5",
1661
- "@img/sharp-wasm32": "0.33.5",
1662
- "@img/sharp-win32-ia32": "0.33.5",
1663
- "@img/sharp-win32-x64": "0.33.5"
1664
- }
1665
- },
1666
- "node_modules/simple-swizzle": {
1667
- "version": "0.2.4",
1668
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
1669
- "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
1670
- "optional": true,
1671
- "dependencies": {
1672
- "is-arrayish": "^0.3.1"
1673
- }
1674
- },
1675
- "node_modules/source-map-js": {
1676
- "version": "1.2.1",
1677
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1678
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1679
- "engines": {
1680
- "node": ">=0.10.0"
1681
- }
1682
- },
1683
- "node_modules/streamsearch": {
1684
- "version": "1.1.0",
1685
- "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
1686
- "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
1687
- "engines": {
1688
- "node": ">=10.0.0"
1689
- }
1690
- },
1691
- "node_modules/styled-jsx": {
1692
- "version": "5.1.6",
1693
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
1694
- "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
1695
- "dependencies": {
1696
- "client-only": "0.0.1"
1697
- },
1698
- "engines": {
1699
- "node": ">= 12.0.0"
1700
- },
1701
- "peerDependencies": {
1702
- "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
1703
- },
1704
- "peerDependenciesMeta": {
1705
- "@babel/core": {
1706
- "optional": true
1707
- },
1708
- "babel-plugin-macros": {
1709
- "optional": true
1710
- }
1711
- }
1712
- },
1713
- "node_modules/sucrase": {
1714
- "version": "3.35.1",
1715
- "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
1716
- "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
1717
- "dev": true,
1718
- "dependencies": {
1719
- "@jridgewell/gen-mapping": "^0.3.2",
1720
- "commander": "^4.0.0",
1721
- "lines-and-columns": "^1.1.6",
1722
- "mz": "^2.7.0",
1723
- "pirates": "^4.0.1",
1724
- "tinyglobby": "^0.2.11",
1725
- "ts-interface-checker": "^0.1.9"
1726
- },
1727
- "bin": {
1728
- "sucrase": "bin/sucrase",
1729
- "sucrase-node": "bin/sucrase-node"
1730
- },
1731
- "engines": {
1732
- "node": ">=16 || 14 >=14.17"
1733
- }
1734
- },
1735
- "node_modules/supports-preserve-symlinks-flag": {
1736
- "version": "1.0.0",
1737
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
1738
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
1739
- "dev": true,
1740
- "engines": {
1741
- "node": ">= 0.4"
1742
- },
1743
- "funding": {
1744
- "url": "https://github.com/sponsors/ljharb"
1745
- }
1746
- },
1747
- "node_modules/tailwindcss": {
1748
- "version": "3.4.19",
1749
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
1750
- "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
1751
- "dev": true,
1752
- "dependencies": {
1753
- "@alloc/quick-lru": "^5.2.0",
1754
- "arg": "^5.0.2",
1755
- "chokidar": "^3.6.0",
1756
- "didyoumean": "^1.2.2",
1757
- "dlv": "^1.1.3",
1758
- "fast-glob": "^3.3.2",
1759
- "glob-parent": "^6.0.2",
1760
- "is-glob": "^4.0.3",
1761
- "jiti": "^1.21.7",
1762
- "lilconfig": "^3.1.3",
1763
- "micromatch": "^4.0.8",
1764
- "normalize-path": "^3.0.0",
1765
- "object-hash": "^3.0.0",
1766
- "picocolors": "^1.1.1",
1767
- "postcss": "^8.4.47",
1768
- "postcss-import": "^15.1.0",
1769
- "postcss-js": "^4.0.1",
1770
- "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
1771
- "postcss-nested": "^6.2.0",
1772
- "postcss-selector-parser": "^6.1.2",
1773
- "resolve": "^1.22.8",
1774
- "sucrase": "^3.35.0"
1775
- },
1776
- "bin": {
1777
- "tailwind": "lib/cli.js",
1778
- "tailwindcss": "lib/cli.js"
1779
- },
1780
- "engines": {
1781
- "node": ">=14.0.0"
1782
- }
1783
- },
1784
- "node_modules/thenify": {
1785
- "version": "3.3.1",
1786
- "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
1787
- "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
1788
- "dev": true,
1789
- "dependencies": {
1790
- "any-promise": "^1.0.0"
1791
- }
1792
- },
1793
- "node_modules/thenify-all": {
1794
- "version": "1.6.0",
1795
- "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
1796
- "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
1797
- "dev": true,
1798
- "dependencies": {
1799
- "thenify": ">= 3.1.0 < 4"
1800
- },
1801
- "engines": {
1802
- "node": ">=0.8"
1803
- }
1804
- },
1805
- "node_modules/tinyglobby": {
1806
- "version": "0.2.15",
1807
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
1808
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
1809
- "dev": true,
1810
- "dependencies": {
1811
- "fdir": "^6.5.0",
1812
- "picomatch": "^4.0.3"
1813
- },
1814
- "engines": {
1815
- "node": ">=12.0.0"
1816
- },
1817
- "funding": {
1818
- "url": "https://github.com/sponsors/SuperchupuDev"
1819
- }
1820
- },
1821
- "node_modules/tinyglobby/node_modules/fdir": {
1822
- "version": "6.5.0",
1823
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
1824
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
1825
- "dev": true,
1826
- "engines": {
1827
- "node": ">=12.0.0"
1828
- },
1829
- "peerDependencies": {
1830
- "picomatch": "^3 || ^4"
1831
- },
1832
- "peerDependenciesMeta": {
1833
- "picomatch": {
1834
- "optional": true
1835
- }
1836
- }
1837
- },
1838
- "node_modules/tinyglobby/node_modules/picomatch": {
1839
- "version": "4.0.3",
1840
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
1841
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
1842
- "dev": true,
1843
- "engines": {
1844
- "node": ">=12"
1845
- },
1846
- "funding": {
1847
- "url": "https://github.com/sponsors/jonschlinkert"
1848
- }
1849
- },
1850
- "node_modules/to-regex-range": {
1851
- "version": "5.0.1",
1852
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1853
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1854
- "dev": true,
1855
- "dependencies": {
1856
- "is-number": "^7.0.0"
1857
- },
1858
- "engines": {
1859
- "node": ">=8.0"
1860
- }
1861
- },
1862
- "node_modules/ts-interface-checker": {
1863
- "version": "0.1.13",
1864
- "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
1865
- "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
1866
- "dev": true
1867
- },
1868
- "node_modules/tslib": {
1869
- "version": "2.8.1",
1870
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
1871
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
1872
- },
1873
- "node_modules/typescript": {
1874
- "version": "5.9.3",
1875
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1876
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1877
- "dev": true,
1878
- "bin": {
1879
- "tsc": "bin/tsc",
1880
- "tsserver": "bin/tsserver"
1881
- },
1882
- "engines": {
1883
- "node": ">=14.17"
1884
- }
1885
- },
1886
- "node_modules/undici-types": {
1887
- "version": "6.21.0",
1888
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
1889
- "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
1890
- "dev": true
1891
- },
1892
- "node_modules/update-browserslist-db": {
1893
- "version": "1.2.3",
1894
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
1895
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
1896
- "dev": true,
1897
- "funding": [
1898
- {
1899
- "type": "opencollective",
1900
- "url": "https://opencollective.com/browserslist"
1901
- },
1902
- {
1903
- "type": "tidelift",
1904
- "url": "https://tidelift.com/funding/github/npm/browserslist"
1905
- },
1906
- {
1907
- "type": "github",
1908
- "url": "https://github.com/sponsors/ai"
1909
- }
1910
- ],
1911
- "dependencies": {
1912
- "escalade": "^3.2.0",
1913
- "picocolors": "^1.1.1"
1914
- },
1915
- "bin": {
1916
- "update-browserslist-db": "cli.js"
1917
- },
1918
- "peerDependencies": {
1919
- "browserslist": ">= 4.21.0"
1920
- }
1921
- },
1922
- "node_modules/util-deprecate": {
1923
- "version": "1.0.2",
1924
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1925
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
1926
- "dev": true
1927
- }
1928
- }
1929
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/package.json DELETED
@@ -1,25 +0,0 @@
1
- {
2
- "name": "anifind",
3
- "version": "0.1.0",
4
- "private": true,
5
- "scripts": {
6
- "dev": "next dev",
7
- "build": "next build",
8
- "start": "next start",
9
- "lint": "next lint"
10
- },
11
- "dependencies": {
12
- "next": "15.1.0",
13
- "react": "^19.0.0",
14
- "react-dom": "^19.0.0"
15
- },
16
- "devDependencies": {
17
- "@types/node": "^20",
18
- "@types/react": "^19",
19
- "@types/react-dom": "^19",
20
- "autoprefixer": "^10.0.1",
21
- "postcss": "^8",
22
- "tailwindcss": "^3.4.1",
23
- "typescript": "^5"
24
- }
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/postcss.config.mjs DELETED
@@ -1,7 +0,0 @@
1
- const config = {
2
- plugins: {
3
- tailwindcss: {},
4
- autoprefixer: {},
5
- },
6
- };
7
- export default config;
 
 
 
 
 
 
 
 
ui/tailwind.config.ts DELETED
@@ -1,11 +0,0 @@
1
- /** @type {import('tailwindcss').Config} */
2
- const config = {
3
- content: [
4
- "./pages/**/*.{js,ts,jsx,tsx,mdx}",
5
- "./components/**/*.{js,ts,jsx,tsx,mdx}",
6
- "./app/**/*.{js,ts,jsx,tsx,mdx}",
7
- ],
8
- theme: { extend: {} },
9
- plugins: [],
10
- };
11
- export default config;
 
 
 
 
 
 
 
 
 
 
 
 
ui/tsconfig.json DELETED
@@ -1,40 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "lib": [
4
- "dom",
5
- "dom.iterable",
6
- "esnext"
7
- ],
8
- "allowJs": true,
9
- "skipLibCheck": true,
10
- "strict": true,
11
- "noEmit": true,
12
- "esModuleInterop": true,
13
- "module": "esnext",
14
- "moduleResolution": "bundler",
15
- "resolveJsonModule": true,
16
- "isolatedModules": true,
17
- "jsx": "preserve",
18
- "incremental": true,
19
- "plugins": [
20
- {
21
- "name": "next"
22
- }
23
- ],
24
- "paths": {
25
- "@/*": [
26
- "./*"
27
- ]
28
- },
29
- "target": "ES2017"
30
- },
31
- "include": [
32
- "next-env.d.ts",
33
- "**/*.ts",
34
- "**/*.tsx",
35
- ".next/types/**/*.ts"
36
- ],
37
- "exclude": [
38
- "node_modules"
39
- ]
40
- }