diff --git a/frontend/src/components/analyze/AnalyzePanel.tsx b/frontend/src/components/analyze/AnalyzePanel.tsx index 199eedf..39890f0 100644 --- a/frontend/src/components/analyze/AnalyzePanel.tsx +++ b/frontend/src/components/analyze/AnalyzePanel.tsx @@ -15,63 +15,97 @@ import { Zap, Globe, Calendar, - Link2, Radio, Eye, ChevronDown, ChevronUp, CheckCircle2, XCircle, + Sparkles, + Target, + Coins, + ShoppingCart, + Ban, + AlertCircle, + Info, + Bookmark, + ArrowRight, } from 'lucide-react' import { api } from '@/lib/api' import { useAnalyzePanelStore } from '@/lib/analyze-store' import type { AnalyzeResponse, AnalyzeSection, AnalyzeItem } from '@/components/analyze/types' +// ============================================================================ +// KNOWN TRADEMARKS (for warning) +// ============================================================================ + +const KNOWN_TRADEMARKS = [ + 'google', 'facebook', 'meta', 'apple', 'microsoft', 'amazon', 'netflix', 'spotify', + 'nike', 'adidas', 'puma', 'gucci', 'louis vuitton', 'chanel', 'rolex', 'omega', + 'tesla', 'bmw', 'mercedes', 'audi', 'porsche', 'ferrari', 'lamborghini', + 'coca cola', 'pepsi', 'mcdonalds', 'starbucks', 'burger king', 'subway', + 'disney', 'marvel', 'pixar', 'warner', 'paramount', 'universal', + 'visa', 'mastercard', 'paypal', 'stripe', 'shopify', 'airbnb', + 'twitter', 'instagram', 'tiktok', 'snapchat', 'linkedin', 'whatsapp', + 'youtube', 'twitch', 'reddit', 'pinterest', 'dropbox', 'slack', 'zoom', + 'samsung', 'sony', 'lg', 'nintendo', 'playstation', 'xbox', 'nvidia', 'intel', 'amd', + 'ibm', 'oracle', 'salesforce', 'adobe', 'autodesk', 'atlassian', + 'swisscom', 'sunrise', 'salt', 'ubs', 'credit suisse', 'zurich', 'swiss re', + 'migros', 'coop', 'denner', 'lidl', 'aldi', 'sbb', 'post', 'swiss', +] + +function checkTrademarkRisk(domain: string): { risk: boolean; match: string | null } { + const name = domain.split('.')[0].toLowerCase().replace(/[-_0-9]/g, '') + for (const tm of KNOWN_TRADEMARKS) { + const cleanTm = tm.replace(/\s+/g, '') + if (name.includes(cleanTm) || cleanTm.includes(name)) { + return { risk: true, match: tm } + } + } + return { risk: false, match: null } +} + // ============================================================================ // HELPERS // ============================================================================ -function getStatusColor(status: string) { +function getScoreColor(score: number) { + if (score >= 80) return 'text-accent' + if (score >= 60) return 'text-emerald-400' + if (score >= 40) return 'text-amber-400' + return 'text-red-400' +} + +function getScoreBg(score: number) { + if (score >= 80) return 'bg-accent/20 border-accent/40' + if (score >= 60) return 'bg-emerald-500/20 border-emerald-500/40' + if (score >= 40) return 'bg-amber-500/20 border-amber-500/40' + return 'bg-red-500/20 border-red-500/40' +} + +function getRecommendation(score: number, trademarkRisk: boolean, isAvailable: boolean) { + if (trademarkRisk) return { label: 'RISKY', color: 'text-red-400 bg-red-500/20 border-red-500/40', icon: Ban } + if (!isAvailable) return { label: 'TAKEN', color: 'text-white/40 bg-white/10 border-white/20', icon: XCircle } + if (score >= 75) return { label: 'BUY', color: 'text-accent bg-accent/20 border-accent/40', icon: ShoppingCart } + if (score >= 50) return { label: 'CONSIDER', color: 'text-amber-400 bg-amber-500/20 border-amber-500/40', icon: Eye } + return { label: 'SKIP', color: 'text-white/40 bg-white/10 border-white/20', icon: Ban } +} + +function getStatusStyle(status: string) { switch (status) { - case 'pass': - return { bg: 'bg-accent/20', text: 'text-accent', border: 'border-accent/40', icon: CheckCircle2 } - case 'warn': - return { bg: 'bg-amber-400/20', text: 'text-amber-300', border: 'border-amber-400/40', icon: AlertTriangle } - case 'fail': - return { bg: 'bg-red-500/20', text: 'text-red-400', border: 'border-red-500/40', icon: XCircle } - default: - return { bg: 'bg-white/10', text: 'text-white/50', border: 'border-white/20', icon: null } + case 'pass': return { bg: 'bg-accent/10', text: 'text-accent', border: 'border-accent/30', icon: CheckCircle2 } + case 'warn': return { bg: 'bg-amber-400/10', text: 'text-amber-400', border: 'border-amber-400/30', icon: AlertTriangle } + case 'fail': return { bg: 'bg-red-500/10', text: 'text-red-400', border: 'border-red-500/30', icon: XCircle } + case 'info': return { bg: 'bg-blue-500/10', text: 'text-blue-400', border: 'border-blue-500/30', icon: Info } + default: return { bg: 'bg-white/5', text: 'text-white/40', border: 'border-white/10', icon: null } } } -function getSectionIcon(key: string) { - switch (key) { - case 'authority': - return Shield - case 'market': - return TrendingUp - case 'risk': - return AlertTriangle - case 'value': - return DollarSign - default: - return Globe - } -} - -function getSectionColor(key: string) { - switch (key) { - case 'authority': - return { text: 'text-blue-400', bg: 'bg-blue-500/10', border: 'border-blue-500/30' } - case 'market': - return { text: 'text-emerald-400', bg: 'bg-emerald-500/10', border: 'border-emerald-500/30' } - case 'risk': - return { text: 'text-amber-400', bg: 'bg-amber-500/10', border: 'border-amber-500/30' } - case 'value': - return { text: 'text-violet-400', bg: 'bg-violet-500/10', border: 'border-violet-500/30' } - default: - return { text: 'text-white/60', bg: 'bg-white/5', border: 'border-white/20' } - } +const SECTION_CONFIG: Record = { + authority: { icon: Shield, color: 'blue', label: 'Authority' }, + market: { icon: TrendingUp, color: 'emerald', label: 'Market' }, + risk: { icon: AlertTriangle, color: 'amber', label: 'Risk' }, + value: { icon: DollarSign, color: 'violet', label: 'Value' }, } async function copyToClipboard(text: string) { @@ -92,35 +126,19 @@ function formatValue(value: unknown): string { return 'Details' } -function isMatrix(item: AnalyzeItem) { - return item.key === 'tld_matrix' && Array.isArray(item.value) -} - // ============================================================================ // COMPONENT // ============================================================================ export function AnalyzePanel() { - const { - isOpen, - domain, - close, - fastMode, - setFastMode, - sectionVisibility, - setSectionVisibility - } = useAnalyzePanelStore() + const { isOpen, domain, close, fastMode, setFastMode } = useAnalyzePanelStore() const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [data, setData] = useState(null) const [copied, setCopied] = useState(false) - const [expandedSections, setExpandedSections] = useState>({ - authority: true, - market: true, - risk: true, - value: true - }) + const [activeSection, setActiveSection] = useState('authority') + const [yieldIntent, setYieldIntent] = useState(null) const refresh = useCallback(async () => { if (!domain) return @@ -129,6 +147,11 @@ export function AnalyzePanel() { try { const res = await api.analyzeDomain(domain, { fast: fastMode, refresh: true }) setData(res) + // Also fetch yield intent + try { + const yieldRes = await api.analyzeYieldDomain(domain) + setYieldIntent(yieldRes) + } catch { setYieldIntent(null) } } catch (e) { setError(e instanceof Error ? e.message : String(e)) setData(null) @@ -143,13 +166,16 @@ export function AnalyzePanel() { const run = async () => { setLoading(true) setError(null) + setYieldIntent(null) try { - const res = await api.analyzeDomain(domain, { fast: fastMode, refresh: false }) - if (!cancelled) setData(res) - } catch (e) { + const [res, yieldRes] = await Promise.allSettled([ + api.analyzeDomain(domain, { fast: fastMode, refresh: false }), + api.analyzeYieldDomain(domain), + ]) if (!cancelled) { - setError(e instanceof Error ? e.message : String(e)) - setData(null) + if (res.status === 'fulfilled') setData(res.value) + else setError(res.reason?.message || 'Analysis failed') + if (yieldRes.status === 'fulfilled') setYieldIntent(yieldRes.value) } } finally { if (!cancelled) setLoading(false) @@ -162,28 +188,15 @@ export function AnalyzePanel() { // ESC to close useEffect(() => { if (!isOpen) return - const onKey = (ev: KeyboardEvent) => { - if (ev.key === 'Escape') close() - } + const onKey = (ev: KeyboardEvent) => { if (ev.key === 'Escape') close() } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) }, [isOpen, close]) - const toggleSection = useCallback((key: string) => { - setExpandedSections(prev => ({ ...prev, [key]: !prev[key] })) - }, []) - - const visibleSections = useMemo(() => { - const sections = data?.sections || [] - const order = ['authority', 'market', 'risk', 'value'] - return [...sections] - .sort((a, b) => order.indexOf(a.key) - order.indexOf(b.key)) - .filter((s) => sectionVisibility[s.key] !== false) - }, [data, sectionVisibility]) - - // Calculate overall score - const overallScore = useMemo(() => { - if (!data?.sections) return null + // Calculate Pounce Score from data + const pounceScore = useMemo(() => { + if (!data?.sections) return 50 + let score = 50 let pass = 0, warn = 0, fail = 0 data.sections.forEach(s => { s.items.forEach(item => { @@ -193,11 +206,27 @@ export function AnalyzePanel() { }) }) const total = pass + warn + fail - if (total === 0) return null - const score = Math.round((pass * 100 + warn * 50) / total) - return { score, pass, warn, fail, total } + if (total > 0) score = Math.round((pass * 100 + warn * 50) / total) + return Math.min(100, Math.max(0, score)) }, [data]) + // Check trademark risk + const trademark = useMemo(() => checkTrademarkRisk(domain || ''), [domain]) + + // Is available? + const isAvailable = useMemo(() => { + const availItem = data?.sections + ?.find(s => s.key === 'authority') + ?.items.find(i => i.key === 'availability') + return availItem?.value === 'available' + }, [data]) + + // Recommendation + const recommendation = useMemo( + () => getRecommendation(pounceScore, trademark.risk, isAvailable), + [pounceScore, trademark.risk, isAvailable] + ) + const headerDomain = data?.domain || domain || '' if (!isOpen) return null @@ -205,28 +234,30 @@ export function AnalyzePanel() { return (
{/* Backdrop */} -
+
- {/* Panel - WIDER & MORE READABLE */} -
+ {/* Panel */} +
- {/* Header */} -
+ {/* ════════════════════════════════════════════════════════════════════ */} + {/* HEADER */} + {/* ════════════════════════════════════════════════════════════════════ */} +
{/* Top Bar */} -
-
-
- +
+
+
+
-
-
Domain Analysis
-
+
+
Domain Analysis
+
{headerDomain}
-
+
- +
- {/* Score Bar - LARGER */} - {overallScore && !loading && ( -
-
-
= 70 ? "text-accent" : overallScore.score >= 40 ? "text-amber-400" : "text-red-400" - )}> - {overallScore.score} -
-
-
Overall Score
-
-
-
-
+ {/* ════════════════════════════════════════════════════════════════════ */} + {/* HERO: Score + Recommendation */} + {/* ════════════════════════════════════════════════════════════════════ */} + {!loading && data && ( +
+
+ {/* Pounce Score */} +
+
+ Pounce Score + +
+
+ {pounceScore} +
+
+ {pounceScore >= 80 ? 'Excellent' : pounceScore >= 60 ? 'Good' : pounceScore >= 40 ? 'Fair' : 'Poor'}
-
- {overallScore.pass} - {overallScore.warn} - {overallScore.fail} + + {/* Recommendation Badge */} +
+ + {recommendation.label}
+ + {/* Trademark Warning */} + {trademark.risk && ( +
+ +
+
Trademark Risk Detected
+
+ Contains "{trademark.match}" - potential legal issues. Research before buying. +
+
+
+ )} + + {/* Yield Intent Tip */} + {yieldIntent && yieldIntent.monetization_potential !== 'low' && ( +
+ +
+
+ Yield Potential + + {yieldIntent.monetization_potential} + +
+
+ {yieldIntent.intent?.category?.replace(/_/g, ' ')} + {yieldIntent.intent?.suggested_partners?.length > 0 && ( + → {yieldIntent.intent.suggested_partners.slice(0, 2).join(', ')} + )} +
+
+ +
+ )}
)} - {/* Mode Toggle */} -
- - {data?.cached && ( - - ⚡ Cached - - )} -
+ {/* ════════════════════════════════════════════════════════════════════ */} + {/* SECTION TABS */} + {/* ════════════════════════════════════════════════════════════════════ */} + {!loading && data && ( +
+ {data.sections.map((section) => { + const config = SECTION_CONFIG[section.key] || SECTION_CONFIG.authority + const isActive = activeSection === section.key + const colorMap: Record = { + blue: { active: 'border-blue-500 bg-blue-500/10 text-blue-400', inactive: 'border-white/[0.06] text-white/40' }, + emerald: { active: 'border-emerald-500 bg-emerald-500/10 text-emerald-400', inactive: 'border-white/[0.06] text-white/40' }, + amber: { active: 'border-amber-500 bg-amber-500/10 text-amber-400', inactive: 'border-white/[0.06] text-white/40' }, + violet: { active: 'border-violet-500 bg-violet-500/10 text-violet-400', inactive: 'border-white/[0.06] text-white/40' }, + } + const colors = colorMap[config.color] || colorMap.blue + + return ( + + ) + })} +
+ )}
- {/* Body - BETTER SPACING */} + {/* ════════════════════════════════════════════════════════════════════ */} + {/* CONTENT */} + {/* ════════════════════════════════════════════════════════════════════ */}
{loading ? ( -
+
- -
Analyzing domain...
+ +
Analyzing domain...
) : error ? ( -
-
-
Analysis Failed
-
{error}
+
+
+
Analysis Failed
+
{error}
) : !data ? ( -
-
No data available
+
+
No data available
) : ( -
- {visibleSections.map((section) => { - const SectionIcon = getSectionIcon(section.key) - const sectionStyle = getSectionColor(section.key) - const isExpanded = expandedSections[section.key] !== false +
+ {/* Active Section Items */} + {data.sections.filter(s => s.key === activeSection).map((section) => ( +
+ {section.items.map((item) => { + const statusStyle = getStatusStyle(item.status) + const StatusIcon = statusStyle.icon - return ( -
- {/* Section Header - LARGER */} - - - {/* Section Items - BETTER CONTRAST */} - {isExpanded && ( -
- {section.items.map((item) => { - const statusStyle = getStatusColor(item.status) - const StatusIcon = statusStyle.icon - - return ( -
-
- {/* Status Indicator - LARGER */} -
- {StatusIcon && } -
- - {/* Content - BETTER READABILITY */} -
-
- - {item.label} - - - {item.source} - -
- - {/* Value - LARGER TEXT */} -
- {isMatrix(item) ? ( -
- {(item.value as any[]).slice(0, 12).map((row: any) => ( -
- {String(row.domain)} - {row.status === 'available' && } -
- ))} -
- ) : ( -
- {formatValue(item.value)} -
- )} -
- - {/* Details Toggle */} - {item.details && Object.keys(item.details).length > 0 && ( -
- - View raw details - -
-                                        {JSON.stringify(item.details, null, 2)}
-                                      
-
- )} -
+ // Special handling for TLD Matrix + if (item.key === 'tld_matrix' && Array.isArray(item.value)) { + return ( +
+
{item.label}
+
+ {(item.value as any[]).slice(0, 12).map((row: any) => ( +
+ .{row.tld} + {row.status === 'available' && }
+ ))} +
+
+ ) + } + + return ( +
+
+ {/* Status Icon */} +
+ {StatusIcon && } +
+ + {/* Content */} +
+
+ {item.label} + {item.source}
- ) - })} + +
+ {formatValue(item.value)} +
+ + {/* Radio Test Details */} + {item.key === 'radio_test' && item.details && ( +
+ {item.details.syllables !== undefined && ( + + {item.details.syllables} syllables + + )} + {item.details.length !== undefined && ( + + {item.details.length} chars + + )} + {item.details.has_hyphen && ( + + has hyphen + + )} + {item.details.has_digits && ( + + has digits + + )} +
+ )} + + {/* Registrar Details */} + {(item.key === 'tld_cheapest_register_usd' || item.key === 'tld_cheapest_renew_usd') && item.details?.registrar && ( +
+ via {item.details.registrar} +
+ )} +
+
- )} -
- ) - })} + ) + })} +
+ ))} + + {/* Quick Actions */} +
+
Quick Actions
+
+ + {isAvailable && yieldIntent?.monetization_potential !== 'low' && ( + + )} +
+
+ + {/* Mode Toggle */} +
+ + {data?.cached && ( + + ⚡ Cached + + )} +
)}
diff --git a/pounce_user.md b/pounce_user.md new file mode 100644 index 0000000..a9f8466 --- /dev/null +++ b/pounce_user.md @@ -0,0 +1,89 @@ +Das ist der finale Schritt. Wir fügen jetzt die **Business-Intelligenz** von BrandBucket (Margot Bushnaq) hinzu. + +Chris Koerner (Video 1) ist der **Jäger** (Offense). +Der Blogger ist der **Analyst** (Defense). +Margot (Video 2) ist die **CFO/Händlerin** (Sustainability). + +Wenn wir ihre Insights integrieren, wird Pounce von einem "Tool für Zocker" zu einer **"Plattform für Domain-Unternehmer"**. Das macht es "Stickier" (Kundenbindung) und schützt die User vor dem Bankrott. + +Hier ist das **finale, angereicherte Unicorn-Konzept**. Es vereint **Jagd, Analyse, Cashflow und Finanzplanung**. + +--- + +### THE POUNCE ALPHA TERMINAL (Final Master Plan) + +**Der Pitch:** "Pounce combines the hunter's instinct, the analyst's diligence, and the merchant's discipline into one AI Operating System." + +#### 1. DISCOVERY: The Opportunity Engine +*Hier finden wir Assets, die andere übersehen.* + +* **Feature A: "The $5 Closeout Sniper" (Koerner)** + * *Logik:* Filtert Domains < $10, 5+ Jahre alt, Backlinks. "Free Money". +* **Feature B: "The Viral Trend Scanner" (Koerner)** + * *Logik:* Google Trends API + Domain Availability. "Buy the Trend". +* **Feature C: "The Typo Generator" (Koerner)** + * *Logik:* Phonetische Varianten von großen Marken. Traffic-Grabber. +* **Feature D: "The Brandable Forge" (BrandBucket - NEU)** 💎 + * *Insight:* In Rezessionen kaufen Firmen "Invented Names" (Fantasienamen wie Zillow), keine Keywords. + * *Pounce Funktion:* Ein Generator für 5-Letter CVCVC-Pattern (Konsonant-Vokal...). + * *Check:* Verfügbar? Aussprechbar? + * *Benefit:* Findet die "Rolex" von morgen für $10. +* **Feature E: "The Agent Hunter" (BrandBucket - NEU)** 💎 + * *Insight:* Zukünftige Namen sind für AI Agents (kurz, menschlich, "Hey Siri"). + * *Pounce Funktion:* Filtert nach 1-2 Silben, klingt wie ein Vorname. + * *Benefit:* Die perfekte Wette auf die AI-Zukunft. + +#### 2. ANALYSIS: The Deep Diligence Deck +*Hier verhindern wir Fehlkäufe.* + +* **Feature F: "The 9-in-1 Dashboard" (Blogger)** + * *Logik:* Keyword Volume, CPC, Trademark Check, Wayback Machine. Alles auf einen Blick. +* **Feature G: "The Authority Score" (Koerner)** + * *Logik:* Unterscheidet "echte" Backlinks (Wikipedia) von Spam. +* **Feature H: "The Radio Test AI" (Koerner & BrandBucket)** + * *Logik:* Margot und Chris sind sich einig: Aussprechbarkeit ist alles. + * *Pounce Funktion:* AI zählt Silben & bewertet "Spelling Confusion". + * *Display:* "🗣️ **Radio Test:** 100/100 (Klingt wie es geschrieben wird)." + +#### 3. STRATEGY: The Yield & Pricing Engine +*Hier machen wir Geld.* + +* **Feature I: "The Yield Strategist" (Koerner)** + * *Logik:* Pounce schlägt vor: "Route zu Amazon Affiliate" oder "Route zu SaaS Partnerprogramm". +* **Feature J: "The Fixed Price Oracle" (BrandBucket - NEU)** 💎 + * *Insight:* "Make Offer" verwirrt Käufer. Festpreise verkaufen sich besser. + * *Pounce Funktion:* Pounce analysiert Comps (Vergleichsverkäufe) und gibt dir keinen "Schätzwert", sondern einen **konkreten Listenpreis**. + * *Output:* "List this for **$2,499**. Do not use 'Make Offer'." + * *Benefit:* Nimmt dem User die Unsicherheit beim Pricing. + +#### 4. EXECUTION: The Portfolio Guard (CFO Mode) +*Hier verhindern wir, dass der User pleite geht.* + +* **Feature K: "Trend Alert Watchlist" (Koerner)** + * *Logik:* Überwacht Themen ("AI Agents") statt nur Domains. +* **Feature L: "The Runway Monitor" (BrandBucket - NEU)** 💎 + * *Insight:* Anfänger kaufen zu viel und sterben an den Renewal-Gebühren nach 12 Monaten. + * *Pounce Funktion:* Ein Finanz-Dashboard. + * *Display:* "⚠️ **Burn Rate Alert:** Du hast $400 Renewals fällig im Oktober. Deine aktuellen Einnahmen (Yield) decken nur $50." + * *Action:* Pounce markiert automatisch Domains zum "Droppen" (Löschen), die keinen Yield und keinen Traffic haben. + * *Benefit:* Pounce rettet dein Business vor dem Cashflow-Tod. + +--- + +### Warum dieses Konzept unschlagbar ist + +Wir decken jetzt den **gesamten Lebenszyklus** eines professionellen Investors ab: + +1. **Kauf:** Wir finden Trends (Koerner) & Brandables (BrandBucket). +2. **Prüfung:** Wir machen den Radio-Test & Backlink-Check. +3. **Haltezeit:** Wir generieren Cashflow durch Yield (Intent Routing) & warnen vor Renewal-Kosten (BrandBucket). +4. **Verkauf:** Wir setzen den perfekten Festpreis (BrandBucket Oracle). + +**Das Feedback an die Domainer:** +Wenn du das nächste Mal mit Chris, dem Blogger oder Yuyu sprichst, sagst du: + +> "Pounce is not just a scanner. It's a **CFO in your pocket**. +> It finds the hidden gems (Koerner), keeps you legally safe (Blogger), and prevents you from going broke on renewals (BrandBucket). +> It tells you what to buy, how to price it, and when to drop it." + +Das ist das Unicorn. 🦄 \ No newline at end of file