import { useState, useEffect, useRef, useCallback } from "react"; // ─── TRANSLATIONS ───────────────────────────────────────────────────────────── const T = { de: { appName: "ScreenBuddy", tagline: "DEIN OTAKU × CINEMA × COMICS KI", categories: "Kategorien entdecken", trending: "Gerade Trending", topRated: "Top Bewertet", crossover: "Crossover Picks", recentSearch: "Zuletzt gesucht", watchlist: "Watchlist", login: "Anmelden", register: "Registrieren", logout: "Abmelden", upgrade: "Premium – 3,99€/Mo", premiumTitle: "ScreenBuddy Premium", searchPlaceholder: "Sag mir was du liebst…", allTitles: "Alle Titel", bestChoice: "Beste Wahl", match: "Match", hype: "Hype", trailer: "Trailer", adLabel: "Anzeige", adText: "Alle Anime ohne Werbung — 30 Tage gratis", adBtn: "Gratis testen", memoryOn: "Memory AN", memoryOff: "Memory AUS", memoryTitle: "Dein Geschmack", lightMode: "Hell", darkMode: "Dunkel", premiumFeatures: ["Keine Werbung", "Sprachmemo", "Bild-Suche (Vision)", "Video-Erkennung"], personalised: "Personalisiert", online: "Online", sendBtn: "Senden", typeHere: "Frag", premiumOnly: "Nur Premium", welcomeBack: "Willkommen zurück", titlesOn: "Titel auf Watchlist", recommendations: "Empfehlungen für Dich", apiKeyMissing: "API-Key fehlt. Bitte konfigurieren.", networkError: "Netzwerkfehler 😅", }, en: { appName: "ScreenBuddy", tagline: "YOUR OTAKU × CINEMA × COMICS AI", categories: "Discover Categories", trending: "Trending Now", topRated: "Top Rated", crossover: "Crossover Picks", recentSearch: "Recently Searched", watchlist: "Watchlist", login: "Sign In", register: "Sign Up", logout: "Sign Out", upgrade: "Premium – €3.99/mo", premiumTitle: "ScreenBuddy Premium", searchPlaceholder: "Tell me what you love…", allTitles: "All Titles", bestChoice: "Best Pick", match: "Match", hype: "Hype", trailer: "Trailer", adLabel: "Ad", adText: "All anime ad-free — 30 days free", adBtn: "Try Free", memoryOn: "Memory ON", memoryOff: "Memory OFF", memoryTitle: "Your Taste", lightMode: "Light", darkMode: "Dark", premiumFeatures: ["No Ads", "Voice Memo", "Image Search (Vision)", "Video Recognition"], personalised: "Personalised", online: "Online", sendBtn: "Send", typeHere: "Ask", premiumOnly: "Premium Only", welcomeBack: "Welcome back", titlesOn: "titles in watchlist", recommendations: "Recommendations For You", apiKeyMissing: "API key missing. Please configure.", networkError: "Network error 😅", }, }; // ─── API CONFIG ─────────────────────────────────────────────────────────────── // In production, use environment variables or a backend proxy const API_CONFIG = { // Set your API key here or use environment variable apiKey: "", // process.env.REACT_APP_ANTHROPIC_API_KEY baseUrl: "[api.anthropic.com](https://api.anthropic.com/v1/messages)", model: "claude-sonnet-4-20250514", }; // API helper function async function callAnthropicAPI(system, messages, maxTokens = 1000) { if (!API_CONFIG.apiKey) { throw new Error("API_KEY_MISSING"); } const response = await fetch(API_CONFIG.baseUrl, { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": API_CONFIG.apiKey, "anthropic-version": "2023-06-01", }, body: JSON.stringify({ model: API_CONFIG.model, max_tokens: maxTokens, system, messages, }), }); if (!response.ok) { throw new Error(`API_ERROR: ${response.status}`); } const data = await response.json(); return data.content?.map(b => b.text || "").join("") || ""; } // ─── ICONS ──────────────────────────────────────────────────────────────────── const Icon = ({ name, size = 18, color = "currentColor" }) => { const icons = { film: <>, tv: <>, star: <>, book: <>, zap: <>, smile: <>, search: <>, user: <>, heart: <>, play: <>, x: <>, home: <>, menu: <>, plus: <>, send: <>, clock: <>, trending: <>, link: <>, arrowright: <>, sparkle: <>, crown: <>, mic: <>, camera: <>, video: <>, brain: <>, sun: <>, moon: <>, globe: <>, settings: <>, image: <>, }; return {icons[name]}; }; // ─── KO SPIRAL MASCOT ──────────────────────────────────────────────────────── function Mascot({ color="#ff6eb4", eyeColor="#1a6aff", size=160, wobble=true, variant="default" }) { const [up, setUp] = useState(false); useEffect(() => { if (!wobble) return; const t = setInterval(() => setUp(v => !v), 2000); return () => clearInterval(t); }, [wobble]); const accessories = { default: null, manga: <>, series: <>, anime: <>⛩️, comics: <>, cartoons: <>🎨, films: <>🎬, }; return (
{accessories[variant]}
); } // ─── CONSTANTS ──────────────────────────────────────────────────────────────── const MASCOT_VARIANTS = [ { color:"#ff6eb4", eye:"#1a6aff" }, { color:"#9b1c4a", eye:"#ff6b00" }, { color:"#7b3cff", eye:"#00e5c4" }, { color:"#00b4d8", eye:"#ff2d78" }, { color:"#43aa8b", eye:"#ffdd00" }, ]; const CATEGORIES = [ { id:"filme", labelDe:"Filme", labelEn:"Films", icon:"film", color:"#ff2d78", accent:"#ff6fa8", mascotVariant:"films", botName:"Cleo", botTitleDe:"Deine Filmexpertin", botTitleEn:"Your Film Expert" }, { id:"serien", labelDe:"Serien", labelEn:"Series", icon:"tv", color:"#00c8ff", accent:"#60dfff", mascotVariant:"series", botName:"Zara", botTitleDe:"Deine Serien-Begleiterin", botTitleEn:"Your Series Guide" }, { id:"anime", labelDe:"Anime", labelEn:"Anime", icon:"star", color:"#ff6b00", accent:"#ffaa55", mascotVariant:"anime", botName:"Hoshi", botTitleDe:"Dein Otaku-Oracle", botTitleEn:"Your Otaku Oracle" }, { id:"manga", labelDe:"Manga", labelEn:"Manga", icon:"book", color:"#9b30ff", accent:"#c080ff", mascotVariant:"manga", botName:"Kira", botTitleDe:"Deine Manga-Meisterin", botTitleEn:"Your Manga Master" }, { id:"comics", labelDe:"Comics", labelEn:"Comics", icon:"zap", color:"#ffdd00", accent:"#ffe944", mascotVariant:"comics", botName:"Axel", botTitleDe:"Dein Comic-Kenner", botTitleEn:"Your Comic Expert" }, { id:"cartoons", labelDe:"Cartoons", labelEn:"Cartoons", icon:"smile", color:"#00e5c4", accent:"#50ffdf", mascotVariant:"cartoons", botName:"Lumi", botTitleDe:"Deine Cartoon-Fee", botTitleEn:"Your Cartoon Fairy" }, ]; const TAG_COLORS = { "Mindfuck":"#ff2d78", "Traurig":"#00c8ff", "Action-Pur":"#ff6b00", "Romance":"#ff4fa3", "Thriller":"#9b30ff", "Philosophisch":"#00e5c4", "Gänsehaut":"#c0ff00", "Dark":"#aaa", "Komödie":"#ffdd00", "Supernatural":"#b060ff" }; const PLAT_COLORS = { Netflix:"#e50914", Prime:"#00a8e1", Crunchyroll:"#ff5500", "Disney+":"#113ccf", "Apple TV+":"#555", HBO:"#8800cc", MangaPlus:"#cc0000", Viz:"#e87000", "Image Comics":"#e8a000", "Marvel Unlimited":"#ed1d24", "DC+":"#0074e8" }; const ALL_CONTENT = { filme:[ { title:"Oppenheimer", genre:"Drama/Biopic", platform:"Prime", rating:4.9, tags:["Gänsehaut","Philosophisch"], year:2023, trailer:"[youtube.com](https://www.youtube.com/watch?v=uYPbbksJxIg)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1531297484001-80022131f5a1?w=400&q=80)" }, { title:"Dune: Part Two", genre:"Sci-Fi/Epic", platform:"Prime", rating:4.7, tags:["Action-Pur","Philosophisch"], year:2024, trailer:"[youtube.com](https://www.youtube.com/watch?v=Way9Dexny3w)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1419242902214-272b3f66ee7a?w=400&q=80)" }, { title:"Everything Everywhere",genre:"Sci-Fi/Komödie", platform:"Disney+", rating:4.8, tags:["Mindfuck","Action-Pur"], year:2022, trailer:"[youtube.com](https://www.youtube.com/watch?v=wxN1T1uxQ2g)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1506318137071-a8e063b4bec0?w=400&q=80)" }, { title:"Past Lives", genre:"Drama/Romance", platform:"Prime", rating:4.6, tags:["Traurig","Romance"], year:2023, trailer:"[youtube.com](https://www.youtube.com/watch?v=5WBnSMoFpFM)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1516589178581-6cd7833ae3b2?w=400&q=80)" }, { title:"Poor Things", genre:"Fantasy/Drama", platform:"Disney+", rating:4.5, tags:["Mindfuck","Dark"], year:2023, trailer:"[youtube.com](https://www.youtube.com/watch?v=RlbR5N6veqw)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1485846234645-a62644f84728?w=400&q=80)" }, ], serien:[ { title:"Adolescence", genre:"Crime/Drama", platform:"Netflix", rating:4.9, tags:["Dark","Gänsehaut"], year:2025, trailer:"[youtube.com](https://www.youtube.com/watch?v=Ik0mFpEBYv0)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1535016120720-40c646be5580?w=400&q=80)" }, { title:"Severance S2", genre:"Sci-Fi/Thriller", platform:"Apple TV+", rating:4.8, tags:["Mindfuck","Dark"], year:2025, trailer:"[youtube.com](https://www.youtube.com/watch?v=xEQP4VVuyrY)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1497366216548-37526070297c?w=400&q=80)" }, { title:"The Last of Us S2", genre:"Drama/Horror", platform:"HBO", rating:4.7, tags:["Traurig","Gänsehaut"], year:2025, trailer:"[youtube.com](https://www.youtube.com/watch?v=iqbCxBXm_-k)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1509347528160-9a9e33742cdb?w=400&q=80)" }, { title:"The Residence", genre:"Comedy/Crime", platform:"Netflix", rating:4.5, tags:["Komödie","Thriller"], year:2025, trailer:"[youtube.com](https://www.youtube.com/watch?v=HG_tknVxOfc)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1489599849927-2ee91cede3ba?w=400&q=80)" }, { title:"White Lotus S3", genre:"Drama/Mystery", platform:"HBO", rating:4.6, tags:["Dark","Thriller"], year:2025, trailer:"[youtube.com](https://www.youtube.com/watch?v=r7M_H2OiEGg)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=400&q=80)" }, ], anime:[ { title:"Sakamoto Days", genre:"Action/Komödie", platform:"Netflix", rating:4.8, tags:["Action-Pur","Komödie"], year:2025, trailer:"[youtube.com](https://www.youtube.com/watch?v=Fj3lCM0KFXI)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=400&q=80)" }, { title:"Dungeon Meshi", genre:"Fantasy/Komödie", platform:"Netflix", rating:4.9, tags:["Komödie","Supernatural"], year:2024, trailer:"[youtube.com](https://www.youtube.com/watch?v=9naJB7D7LPo)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1518611012118-696072aa579a?w=400&q=80)" }, { title:"Solo Leveling S2", genre:"Action/Fantasy", platform:"Crunchyroll",rating:4.7, tags:["Action-Pur","Supernatural"],year:2025, trailer:"[youtube.com](https://www.youtube.com/watch?v=IlgyoZlS3Qs)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1533628635777-112b2239b1c7?w=400&q=80)" }, { title:"Blue Box", genre:"Romance/Sport", platform:"Crunchyroll",rating:4.4, tags:["Romance","Traurig"], year:2024, trailer:"[youtube.com](https://www.youtube.com/watch?v=oqEMBtSn0TI)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1541701494587-cb58502866ab?w=400&q=80)" }, { title:"Frieren", genre:"Fantasy/Drama", platform:"Crunchyroll",rating:5.0, tags:["Traurig","Philosophisch"], year:2024, trailer:"[youtube.com](https://www.youtube.com/watch?v=YFCgBGNTF4Y)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1474552226712-ac0f0961a954?w=400&q=80)" }, ], manga:[ { title:"Chainsaw Man", genre:"Action/Horror", platform:"MangaPlus", rating:4.9, tags:["Dark","Mindfuck"], year:"Laufend", trailer:"[youtube.com](https://www.youtube.com/watch?v=dXlFSif3vYE)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1608231387042-66d1773070a5?w=400&q=80)" }, { title:"Berserk", genre:"Dark Fantasy", platform:"Viz", rating:5.0, tags:["Dark","Philosophisch"], year:"Laufend", trailer:"[youtube.com](https://www.youtube.com/watch?v=Wqwm7sHLfpg)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&q=80)" }, { title:"Oshi no Ko", genre:"Drama/Mystery", platform:"MangaPlus", rating:4.7, tags:["Mindfuck","Traurig"], year:"Laufend", trailer:"[youtube.com](https://www.youtube.com/watch?v=Oc6HRQHiAi0)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1534447677768-be436bb09401?w=400&q=80)" }, { title:"Jujutsu Kaisen", genre:"Action/Supernat.",platform:"MangaPlus", rating:4.6, tags:["Action-Pur","Dark"], year:"Abgeschl.", trailer:"[youtube.com](https://www.youtube.com/watch?v=PKmnLmM8MkI)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1555680202-c86f0e12f086?w=400&q=80)" }, { title:"Kaiju No. 8", genre:"Action/Sci-Fi", platform:"MangaPlus", rating:4.5, tags:["Action-Pur","Supernatural"],year:"Laufend", trailer:"[youtube.com](https://www.youtube.com/watch?v=oUh3gT_KPzg)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1464802686167-b939a6910659?w=400&q=80)" }, ], comics:[ { title:"Ultimate Spider-Man", genre:"Superhero/Action",platform:"Marvel Unlimited",rating:4.9,tags:["Action-Pur","Komödie"],year:2024, trailer:"[youtube.com](https://www.youtube.com/watch?v=JfVOs4VSpmA)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1635805737707-575885ab0820?w=400&q=80)" }, { title:"Gotham by Gaslight", genre:"Noir/Thriller", platform:"DC+", rating:4.7, tags:["Dark","Thriller"], year:"Classic", trailer:"[youtube.com](https://www.youtube.com/watch?v=DkaSo50_Yas)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1531259683007-016a7b628fc3?w=400&q=80)" }, { title:"Saga", genre:"Sci-Fi/Drama", platform:"Image Comics",rating:4.9,tags:["Philosophisch","Traurig"], year:"Laufend", trailer:"[youtube.com](https://www.youtube.com/watch?v=jH2JC3EPfqg)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1543163521-1bf539c55dd2?w=400&q=80)" }, { title:"Invincible", genre:"Superhero/Drama", platform:"Image Comics",rating:4.8,tags:["Action-Pur","Gänsehaut"], year:"Abgeschl.", trailer:"[youtube.com](https://www.youtube.com/watch?v=m9oJiMYO4Ks)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1464802686167-b939a6910659?w=400&q=80)" }, { title:"East of West", genre:"Western/Sci-Fi", platform:"Image Comics",rating:4.6,tags:["Dark","Philosophisch"], year:"Abgeschl.", trailer:"[youtube.com](https://www.youtube.com/watch?v=placeholder)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&q=80)" }, ], cartoons:[ { title:"Arcane S2", genre:"Action/Drama", platform:"Netflix", rating:4.9, tags:["Dark","Gänsehaut"], year:2024, trailer:"[youtube.com](https://www.youtube.com/watch?v=gHGNGMfXSMI)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1574375927938-d5a98e8ffe85?w=400&q=80)" }, { title:"Hazbin Hotel S2", genre:"Musical/Comedy", platform:"Prime", rating:4.6, tags:["Komödie","Dark"], year:2025, trailer:"[youtube.com](https://www.youtube.com/watch?v=yFgCDRDFGLo)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1541701494587-cb58502866ab?w=400&q=80)" }, { title:"The Owl House", genre:"Fantasy/Advent.", platform:"Disney+", rating:4.8, tags:["Supernatural","Romance"], year:"Abgeschl.", trailer:"[youtube.com](https://www.youtube.com/watch?v=NPzL-F6uBNQ)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1474552226712-ac0f0961a954?w=400&q=80)" }, { title:"Rick & Morty S7", genre:"Sci-Fi/Comedy", platform:"Netflix", rating:4.5, tags:["Mindfuck","Komödie"], year:2023, trailer:"[youtube.com](https://www.youtube.com/watch?v=snDHijF-468)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1506318137071-a8e063b4bec0?w=400&q=80)" }, { title:"X-Men '97", genre:"Superhero/Action",platform:"Disney+", rating:4.9, tags:["Action-Pur","Gänsehaut"], year:2024, trailer:"[youtube.com](https://www.youtube.com/watch?v=8V_B2k0CXL8)", img:"[images.unsplash.com](https://images.unsplash.com/photo-1535016120720-40c646be5580?w=400&q=80)" }, ], }; // ─── BUILD SYSTEM PROMPT ────────────────────────────────────────────────────── function buildSystem(catId, watchlist, searchHistory, userName, memory, lang) { const cat = CATEGORIES.find(c => c.id === catId); const botName = cat?.botName || "Buddy"; const isDE = lang === "de"; const systemMap = { filme: isDE ? `Du bist ${botName}, eine charmante Filmexpertin mit Kinoleidenschaft. Empfiehl Filme, erkenne Crossovers, nenne Plattformen. Nie Spoiler.` : `You are ${botName}, a charming film expert. Recommend movies, spot crossovers, mention platforms. No spoilers.`, serien: isDE ? `Du bist ${botName}, die coolste Serien-Begleiterin. Empfiehl westliche TV-Serien (keine Anime). Keine Spoiler.` : `You are ${botName}, the coolest series guide. Recommend western TV shows (no anime). No spoilers.`, anime: isDE ? `Du bist ${botName}, ein begeistertes Otaku-Oracle. Empfiehl Anime, erkenne Crossovers zu westlichen Serien, nenne Plattformen (Crunchyroll, Netflix). Keine Spoiler. Otaku-Energie!` : `You are ${botName}, an enthusiastic otaku oracle. Recommend anime, spot crossovers, mention platforms. No spoilers. Otaku energy!`, manga: isDE ? `Du bist ${botName}, die weise Manga-Meisterin. Empfiehl Manga-Reihen, nenne Leseplattformen (MangaPlus, Viz). Keine Spoiler.` : `You are ${botName}, the wise manga master. Recommend manga series, mention reading platforms. No spoilers.`, comics: isDE ? `Du bist ${botName}, der kenntnisreiche Comic-Kenner. Empfiehl Marvel, DC, Image Comics Runs. Erkenne Crossovers. Keine Spoiler.` : `You are ${botName}, the knowledgeable comic expert. Recommend Marvel, DC, Image Comics runs. Spot crossovers. No spoilers.`, cartoons: isDE ? `Du bist ${botName}, die kreative Cartoon-Fee. Empfiehl westliche Animationsserien und Cartoons. Keine Spoiler.` : `You are ${botName}, the creative cartoon fairy. Recommend western animation and cartoons. No spoilers.`, }; let prompt = systemMap[catId] || systemMap.filme; if (userName) { prompt += isDE ? ` Der Nutzer heißt ${userName}.` : ` The user's name is ${userName}.`; } if (memory?.enabled && (memory.likes?.length || memory.dislikes?.length)) { if (memory.likes?.length) { prompt += isDE ? ` Der Nutzer mag: ${memory.likes.join(", ")}.` : ` User likes: ${memory.likes.join(", ")}.`; } if (memory.dislikes?.length) { prompt += isDE ? ` Der Nutzer mag NICHT: ${memory.dislikes.join(", ")}.` : ` User dislikes: ${memory.dislikes.join(", ")}.`; } } if (watchlist?.length) { prompt += isDE ? ` Watchlist: ${watchlist.map(i => i.title).join(", ")}. Empfiehl keine Titel die schon drauf sind.` : ` Watchlist: ${watchlist.map(i => i.title).join(", ")}. Don't recommend titles already on it.`; } if (searchHistory?.length) { prompt += isDE ? ` Zuletzt gesucht: ${searchHistory.slice(-4).join(", ")}.` : ` Recently searched: ${searchHistory.slice(-4).join(", ")}.`; } prompt += isDE ? " Antworte IMMER auf Deutsch. Max 3-4 Sätze. Nenne immer die Plattform. Wenn du etwas empfiehlst, beschreib kurz warum (spoilerfrei). Zeige Enthusiasmus!" : " Always reply in English. Max 3-4 sentences. Always mention the platform. Briefly explain why you recommend something (no spoilers). Show enthusiasm!"; return prompt; } // ─── SMALL COMPONENTS ───────────────────────────────────────────────────────── const TagBadge = ({ tag }) => { const c = TAG_COLORS[tag] || "#666"; return ( {tag} ); }; const PlatBadge = ({ p }) => ( {p} ); const Stars = ({ r }) => ( {"★".repeat(Math.floor(r))}{"☆".repeat(5 - Math.floor(r))} {r} ); // ─── CARD ───────────────────────────────────────────────────────────────────── function Card({ item, color, onAdd, inList, dark }) { const [hov, setHov] = useState(false); const bg = dark ? "#0d0d1a" : "#f0f0f8"; const border = dark ? (hov ? color + "55" : "#1a1a2e") : (hov ? color + "88" : "#ddd"); const titleColor = dark ? "#fff" : "#111"; return (
setHov(true)} onMouseLeave={() => setHov(false)} style={{ borderRadius: 14, overflow: "hidden", cursor: "pointer", transition: "transform .3s,box-shadow .3s", transform: hov ? "translateY(-8px) scale(1.03)" : "none", boxShadow: hov ? `0 24px 60px ${color}33,0 4px 20px #0005` : "0 4px 16px #0003", background: bg, border: `1px solid ${border}`, flex: "0 0 auto", width: 158 }} >
{item.title}
{hov && ( e.stopPropagation()} style={{ position: "absolute", top: "50%", left: "50%", transform: "translate(-50%,-60%)", background: "#000b", border: "2px solid #fff4", borderRadius: "50%", width: 44, height: 44, display: "flex", alignItems: "center", justifyContent: "center" }} > )}
{item.title}
{item.genre} · {item.year}
{item.tags.slice(0, 2).map(t => )}
); } // ─── AD BANNER ──────────────────────────────────────────────────────────────── function AdBanner({ color = "#ff2d78", dark = true, slot = "horizontal" }) { return (
ANZEIGE
📣 Werbeplatz verfügbar
Deine Werbung hier · ads@screenbuddy.app
BUCHEN
); } // ─── POPUP AD ───────────────────────────────────────────────────────────────── function PopupAd({ onClose, dark }) { return (
e.stopPropagation()} >
WERBUNG
🍿
SCREENBUDDY PREMIUM
Keine Werbung, Vision-Suche, Sprachmemo und mehr für nur 3,99€/Monat
); } // ─── SPOTLIGHT 5 ────────────────────────────────────────────────────────────── function Spotlight({ items, color, watchlist, onAdd, dark, t }) { const sorted = [...items].sort((a, b) => b.rating - a.rating); const best = sorted[0]; const sides = sorted.slice(1, 5); const cardBg = dark ? "#0d0d1a" : "#f0f0f8"; const SmallCard = ({ item }) => (
{item.title}
{item.title}
); return (
{t.recommendations.toUpperCase()}
{sides.slice(0, 2).map(i => )}
{best.title}
{t.bestChoice.toUpperCase()}
{best.title}
{best.genre} · {best.year}
{best.tags.map(tg => )}
{sides.slice(2, 4).map(i => )}
); } // ─── MEMORY PANEL ───────────────────────────────────────────────────────────── function MemoryPanel({ memory, onUpdate, onClose, dark, t }) { const [like, setLike] = useState(""); const [dislike, setDislike] = useState(""); const bg = dark ? "#0d0d1a" : "#fff"; const border = dark ? "#252540" : "#ddd"; const textColor = dark ? "#ddd" : "#333"; const inputStyle = { width: "100%", background: dark ? "#080810" : "#f5f5f5", border: `1px solid ${border}`, borderRadius: 10, color: textColor, fontSize: 13, padding: "9px 12px", outline: "none", fontFamily: "inherit", boxSizing: "border-box" }; const addLike = () => { if (like.trim()) { onUpdate({ ...memory, likes: [...(memory.likes || []), like.trim()] }); setLike(""); } }; const addDislike = () => { if (dislike.trim()) { onUpdate({ ...memory, dislikes: [...(memory.dislikes || []), dislike.trim()] }); setDislike(""); } }; return (
e.stopPropagation()} >
{t.memoryTitle.toUpperCase()}
✓ MAG ICH
{memory.likes?.map((l, i) => ( {l} ))}
setLike(e.target.value)} onKeyDown={e => e.key === "Enter" && addLike()} placeholder="z.B. Action, Mystery…" style={inputStyle} />
✗ MAG ICH NICHT
{memory.dislikes?.map((d, i) => ( {d} ))}
setDislike(e.target.value)} onKeyDown={e => e.key === "Enter" && addDislike()} placeholder="z.B. Horror, Gore…" style={inputStyle} />
); } // ─── PREMIUM MODAL ──────────────────────────────────────────────────────────── function PremiumModal({ onClose, onUpgrade, dark, t }) { const bg = dark ? "#0d0d1a" : "#fff"; const textColor = dark ? "#ddd" : "#333"; const features = [ { icon: "crown", label: t.premiumFeatures[0], desc: dark ? "Genieße ScreenBuddy werbefrei" : "Enjoy ScreenBuddy ad-free" }, { icon: "mic", label: t.premiumFeatures[1], desc: dark ? "Sprich mit dem Bot statt zu tippen" : "Talk to the bot instead of typing" }, { icon: "camera", label: t.premiumFeatures[2], desc: dark ? "Foto hochladen → KI erkennt die Serie" : "Upload photo → AI identifies the series" }, { icon: "video", label: t.premiumFeatures[3], desc: dark ? "Video-Clip beschreiben → KI findet es" : "Describe a video clip → AI finds it" }, ]; return (
e.stopPropagation()} >
👑
{t.premiumTitle.toUpperCase()}
3,99€ / Monat
{features.map(f => (
{f.label}
{f.desc}
))}
); } // ─── VISION UPLOADER ────────────────────────────────────────────────────────── function VisionSearch({ cat, onResult, dark, t }) { const [loading, setLoading] = useState(false); const [preview, setPreview] = useState(null); const fileRef = useRef(null); const handleFile = async (e) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = async (ev) => { const base64Data = ev.target.result.split(",")[1]; setPreview(ev.target.result); setLoading(true); try { if (!API_CONFIG.apiKey) { onResult(t.apiKeyMissing); setLoading(false); return; } const response = await fetch(API_CONFIG.baseUrl, { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": API_CONFIG.apiKey, "anthropic-version": "2023-06-01", }, body: JSON.stringify({ model: API_CONFIG.model, max_tokens: 800, system: `Du bist ein Entertainment-Experte. Erkenne in diesem Bild: Welche Serie, welcher Film, welcher Anime oder welche Comic-Szene ist zu sehen? Nenne Titel, Plattform, und einen YouTube-Trailer-Link wenn möglich. Antworte auf Deutsch, kurz und enthusiastisch.`, messages: [{ role: "user", content: [ { type: "image", source: { type: "base64", media_type: file.type, data: base64Data } }, { type: "text", text: "Was siehst du hier? Welche Serie oder welcher Film ist das?" } ] }] }) }); const data = await response.json(); onResult(data.content?.map(b => b.text || "").join("") || "Konnte das Bild nicht erkennen."); } catch (error) { onResult("Fehler bei der Bildanalyse 😅"); } setLoading(false); }; reader.readAsDataURL(file); }; return (
{preview && ( preview )}
); } // ─── AI CHAT ────────────────────────────────────────────────────────────────── function AIChat({ cat, watchlist, searchHistory, userName, memory, onMemoryUpdate, isPremium, onUpgrade, dark, t, lang }) { const system = buildSystem(cat.id, watchlist, searchHistory, userName, memory, lang); const catObj = CATEGORIES.find(c => c.id === cat.id); const botName = catObj?.botName || "Buddy"; const botTitle = lang === "de" ? catObj?.botTitleDe : catObj?.botTitleEn; const buildGreeting = useCallback(() => { let g = `${botName} ${lang === "de" ? "ist online" : "is online"}`; if (userName) g += ` — ${lang === "de" ? "hey" : "hey"} **${userName}**`; g += "! 👋 "; if (memory?.enabled && memory.likes?.length) { g += lang === "de" ? `Ich weiß du magst ${memory.likes.slice(0, 2).join(" und ")}. ` : `I know you like ${memory.likes.slice(0, 2).join(" and ")}. `; } if (watchlist.length > 0) { g += lang === "de" ? `Du hast ${watchlist.length} Titel auf der Watchlist. Was suchst du heute?` : `You have ${watchlist.length} titles in your watchlist. What are you looking for today?`; } else { g += lang === "de" ? "Was willst du heute schauen?" : "What do you want to watch today?"; } return g; }, [botName, lang, userName, memory, watchlist]); const [msgs, setMsgs] = useState([{ role: "ai", text: buildGreeting() }]); const [input, setInput] = useState(""); const [typing, setTyping] = useState(false); const [showMemory, setShowMemory] = useState(false); const ref = useRef(null); useEffect(() => { setMsgs([{ role: "ai", text: buildGreeting() }]); }, [cat.id, lang, buildGreeting]); useEffect(() => { if (ref.current) ref.current.scrollTop = ref.current.scrollHeight; }, [msgs, typing]); const send = async (overrideText = null) => { const msg = (overrideText || input).trim(); if (!msg) return; setInput(""); setTyping(true); setMsgs(m => [...m, { role: "user", text: msg }]); const history = msgs.filter(m => m.role === "user").map(m => ({ role: "user", content: m.text })); history.push({ role: "user", content: msg }); try { const response = await callAnthropicAPI(system, history); setMsgs(m => [...m, { role: "ai", text: response || "Fehler!" }]); } catch (error) { const errorMsg = error.message === "API_KEY_MISSING" ? t.apiKeyMissing : t.networkError; setMsgs(m => [...m, { role: "ai", text: errorMsg }]); } setTyping(false); }; const handleVisionResult = (result) => { setMsgs(m => [...m, { role: "ai", text: "📸 " + result }]); }; const chatBg = dark ? "#0a0a15" : "#f8f8ff"; const msgBg = dark ? "#111827" : "#eeeeff"; const inputBg = dark ? "#080810" : "#f0f0ff"; const borderColor = dark ? cat.color + "22" : cat.color + "44"; const textColor = dark ? "#ddd" : "#222"; const quickSuggestions = [ watchlist.length > 0 ? (lang === "de" ? `Was passt zu ${watchlist[0].title}?` : `What's similar to ${watchlist[0].title}?`) : (lang === "de" ? "Was ist gerade trending?" : "What's trending now?"), lang === "de" ? "Crossover-Empfehlung" : "Crossover pick", lang === "de" ? "Spoilerfreie Erklärung" : "Spoiler-free summary", lang === "de" ? "Wo kann ich das schauen?" : "Where can I watch it?", ]; return (
{/* Header */}
{botName}
{botTitle}
{memory?.enabled && (
{t.memoryOn}
)}
{t.online}
{/* Messages */}
{msgs.map((m, i) => (
{m.role === "ai" && (
)}
{m.text.replace(/\*\*(.*?)\*\*/g, "$1")}
))} {typing && (
{[0, 1, 2].map(j => (
))}
)}
{/* Quick suggestions */}
{quickSuggestions.map(s => ( ))}
{/* Premium tools row */}
{isPremium ? : ( ) } {!isPremium && ( {t.premiumOnly} )}
{/* Input */}
setInput(e.target.value)} onKeyDown={e => e.key === "Enter" && send()} placeholder={`${t.typeHere} ${botName}…`} style={{ flex: 1, background: inputBg, border: `1px solid ${cat.color}22`, borderRadius: 10, color: dark ? "#ccc" : "#333", fontSize: 12, padding: "9px 12px", outline: "none", fontFamily: "inherit" }} />
{showMemory && ( { onMemoryUpdate(m); setShowMemory(false); }} onClose={() => setShowMemory(false)} dark={dark} t={t} /> )}
); } // ─── AUTH MODAL ─────────────────────────────────────────────────────────────── function AuthModal({ onClose, onLogin, dark, t }) { const [mode, setMode] = useState("login"); const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [pw, setPw] = useState(""); const bg = dark ? "#0d0d1a" : "#fff"; const inputStyle = { width: "100%", background: dark ? "#080810" : "#f5f5f5", border: `1px solid ${dark ? "#252540" : "#ddd"}`, borderRadius: 10, color: dark ? "#ddd" : "#333", fontSize: 13, padding: "11px 14px", outline: "none", fontFamily: "inherit", marginBottom: 10, boxSizing: "border-box" }; const handleSubmit = () => { if (!email) return; onLogin({ name: name || email.split("@")[0], email }); onClose(); }; return (
e.stopPropagation()} >
SCREENBUDDY
{["login", "register"].map(m => ( ))}
{mode === "register" && ( setName(e.target.value)} placeholder="Name" style={inputStyle}/> )} setEmail(e.target.value)} placeholder="E-Mail" style={inputStyle}/> setPw(e.target.value)} type="password" placeholder="Passwort" style={{ ...inputStyle, marginBottom: 18 }} />
— oder —
{["Google", "Apple"].map(p => ( ))}
); } // ─── WATCHLIST PANEL ────────────────────────────────────────────────────────── function WatchlistPanel({ items, onRemove, onClose, dark, t }) { const bg = dark ? "#0a0a15" : "#fff"; return (
e.stopPropagation()} >
{t.watchlist.toUpperCase()} · {items.length}
{items.length === 0 ? (
Noch leer
Klick + auf einer Karte
) : items.map(item => (
{item.title}
{item.title}
{item.genre}
)) }
); } // ─── CATEGORY PAGE ──────────────────────────────────────────────────────────── function CategoryPage({ cat, watchlist, onAdd, userName, onSelectCat, searchHistory, memory, onMemoryUpdate, isPremium, onUpgrade, dark, t, lang }) { const items = ALL_CONTENT[cat.id] || []; const bg = dark ? "#07070f" : "#f9f9ff"; const textColor = dark ? "#ddd" : "#333"; return (
KATEGORIE
{lang === "de" ? cat.labelDe : cat.labelEn}
{items.length} {lang === "de" ? "Titel · empfohlen von" : "Titles · curated by"} {cat.botName}
{CATEGORIES.filter(c => c.id !== cat.id).map(c => ( ))}
{t.allTitles.toUpperCase()}
{items.map(item => ( w.title === item.title)} dark={dark} /> ))}
{items.map((item, i) => (
{item.title}
{78 + (i * 5)}% {t.match}
))}
{!isPremium && } {!isPremium && }
KI-CHAT MIT {cat.botName?.toUpperCase()}
); } // ─── HOME PAGE ──────────────────────────────────────────────────────────────── function HomePage({ onSelectCat, mascotVariant, onChangeMascot, watchlist, onAdd, userName, searchHistory, setSearchHistory, isPremium, onUpgrade, dark, t, lang }) { const [query, setQuery] = useState(""); const [reply, setReply] = useState(""); const [typing, setTyping] = useState(false); const [sugCat, setSugCat] = useState(null); const { color, eye } = MASCOT_VARIANTS[mascotVariant]; const bg = dark ? "#07070f" : "#f9f9ff"; const textColor = dark ? "#ddd" : "#333"; const doSearch = async () => { if (!query.trim()) return; setSearchHistory(h => [...h.filter(x => x !== query), query].slice(-10)); setTyping(true); setReply(""); setSugCat(null); const wlCtx = watchlist.length > 0 ? `Watchlist: ${watchlist.map(i => i.title).join(", ")}.` : ""; try { const system = `Du bist ScreenBuddy, smarte Entertainment-KI. ${wlCtx} Antworte auf ${lang === "de" ? "Deutsch" : "Englisch"}, enthusiastisch, 2-3 Sätze. Empfiehl Kategorie. Format: [Antwort] 🎯 Kategorie: [Filme|Serien|Anime|Manga|Comics|Cartoons]`; const text = await callAnthropicAPI(system, [{ role: "user", content: query }]); const match = text.match(/Kategorie:\s*(Filme|Serien|Anime|Manga|Comics|Cartoons)/i); if (match) { setSugCat(CATEGORIES.find(c => c.labelDe.toLowerCase() === match[1].toLowerCase())); } setReply(text.replace(/🎯\s*Kategorie:.*$/i, "").trim()); } catch (error) { const errorMsg = error.message === "API_KEY_MISSING" ? t.apiKeyMissing : t.networkError; setReply(errorMsg); } setTyping(false); }; const allFlat = Object.values(ALL_CONTENT).flat(); const topRated = [...allFlat].sort((a, b) => b.rating - a.rating).slice(0, 6); const trending = allFlat.filter((_, i) => i % 2 === 0).slice(0, 5); const quickSearches = lang === "de" ? ["Ich liebe Breaking Bad", "Anime für Einsteiger", "Dark Thriller Serien", "Bester Marvel Comic"] : ["I love Breaking Bad", "Anime for beginners", "Dark thriller series", "Best Marvel comic"]; const crossovers = [ { from: "Breaking Bad", to: "Death Note", reason: lang === "de" ? "Anti-Held, moralisches Verfallen, Katz-und-Maus" : "Anti-hero, moral decay, cat-and-mouse", catId: "anime", color: "#ff6b00" }, { from: "True Detective", to: "Monster", reason: lang === "de" ? "Europäischer Serienmörder-Thriller, Meisterwerk" : "European serial killer thriller, masterpiece", catId: "anime", color: "#ff6b00" }, { from: "Inception", to: "Paprika", reason: lang === "de" ? "Träume in Träumen – hat Nolan beeinflusst" : "Dreams in dreams – influenced Nolan", catId: "anime", color: "#ff6b00" }, { from: "The Walking Dead", to: "Invincible", reason: lang === "de" ? "Beide starten harmlos, werden brutal ernst" : "Both start light, get brutally serious", catId: "comics", color: "#ffdd00" }, ]; return (
{MASCOT_VARIANTS.map((v, i) => (
SCREENBUDDY
{t.tagline}
{(userName || watchlist.length > 0) && (
{userName ? `${t.welcomeBack}, ${userName}! ` : ""} {watchlist.length > 0 ? `${watchlist.length} ${t.titlesOn}.` : ""}
)}
setQuery(e.target.value)} onKeyDown={e => e.key === "Enter" && doSearch()} placeholder={t.searchPlaceholder} style={{ width: "100%", background: dark ? "#0d0d1a" : "#eeeeff", border: `1px solid ${dark ? "#1e1e30" : "#ccc"}`, borderRadius: 14, color: textColor, fontSize: 13, padding: "14px 52px 14px 42px", outline: "none", fontFamily: "inherit", boxSizing: "border-box" }} />
{(typing || reply) && (
{typing ? (
{[0, 1, 2].map(j => (
))}
) : ( <>
{reply}
{sugCat && ( )} ) }
)}
{quickSearches.map(s => ( ))}
{/* Category tiles */}
{t.categories.toUpperCase()}
{CATEGORIES.map(cat => { const preview = ALL_CONTENT[cat.id]?.slice(0, 3) || []; return ( ); })}
{!isPremium && } {/* Trending */}
{t.trending.toUpperCase()}
{trending.map(item => { const cat = CATEGORIES.find(c => ALL_CONTENT[c.id]?.find(i => i.title === item.title)) || CATEGORIES[0]; return ( w.title === item.title)} dark={dark} /> ); })}
{trending.map((item, i) => (
{item.title}
{86 + (i * 3)}% {t.hype}
))}
{/* Top rated */}
{t.topRated.toUpperCase()}
{topRated.map(item => { const cat = CATEGORIES.find(c => ALL_CONTENT[c.id]?.find(i => i.title === item.title)) || CATEGORIES[0]; return ( w.title === item.title)} dark={dark} /> ); })}
{/* Crossovers */}
{t.crossover.toUpperCase()}
{crossovers.map(x => ( ))}
{searchHistory.length > 0 && ( <>
{t.recentSearch.toUpperCase()}
{searchHistory.slice(-8).reverse().map((s, i) => ( ))}
)} {!isPremium && }
); } // ─── APP ROOT ───────────────────────────────────────────────────────────────── export default function App() { const [tab, setTab] = useState("home"); const [mascotIdx, setMascotIdx] = useState(0); const [user, setUser] = useState(null); const [isPremium, setIsPremium] = useState(false); const [showAuth, setShowAuth] = useState(false); const [showWL, setShowWL] = useState(false); const [showPremium, setShowPremium] = useState(false); const [showPopupAd, setShowPopupAd] = useState(false); const [watchlist, setWatchlist] = useState([]); const [searchHistory, setSearchHistory] = useState(["Breaking Bad", "Dark Anime"]); const [mobileMenu, setMobileMenu] = useState(false); const [dark, setDark] = useState(true); const [lang, setLang] = useState("de"); const [memory, setMemory] = useState({ enabled: false, likes: [], dislikes: [] }); const t = T[lang]; // Show popup ad after 30 seconds for free users useEffect(() => { if (isPremium) return; const timer = setTimeout(() => setShowPopupAd(true), 30000); return () => clearTimeout(timer); }, [isPremium]); const toggleWL = useCallback((item) => { setWatchlist(w => w.find(i => i.title === item.title) ? w.filter(i => i.title !== item.title) : [...w, item] ); }, []); const handleUpgrade = useCallback((method) => { const msg = method ? `Zahlung via ${method} wird verarbeitet…\n\n(Demo: Premium freigeschaltet ✓)` : "Demo: Premium wird freigeschaltet."; alert(msg); setIsPremium(true); }, []); const activeCat = CATEGORIES.find(c => c.id === tab); const { color: mColor, eye: mEye } = MASCOT_VARIANTS[mascotIdx]; const bg = dark ? "#07070f" : "#f9f9ff"; const navBg = dark ? "#07070fee" : "#ffffffee"; const navBorder = dark ? "#111120" : "#ddd"; const textColor = dark ? "#e0e0e0" : "#222"; return (
{/* ── NAV ── */}
{/* Logo */} {/* Desktop tabs */}
{CATEGORIES.map(cat => { const active = tab === cat.id; return ( ); })}
{/* Right actions */}
{/* Lang */} {/* Dark/Light */} {/* Premium */} {!isPremium && ( )} {isPremium && (
PRO
)} {/* Watchlist */} {/* User */} {/* Mobile menu */}
{/* Mobile dropdown */}
{CATEGORIES.map(cat => ( ))}
{/* ── PAGES ── */}
{tab === "home" ? ( { setTab(t2); setMobileMenu(false); }} mascotVariant={mascotIdx} onChangeMascot={setMascotIdx} watchlist={watchlist} onAdd={toggleWL} userName={user?.name} searchHistory={searchHistory} set