Hunt Module UI Optimization: unified award-winning design
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled

- Unified table headers, rows, and action alignments across all tabs
- Enhanced SearchTab: improved result display and global TLD grid
- Refined DropsTab: standardized table layouts and mobile views
- Optimized AuctionsTab: improved column alignment and source badges
- Modernized TrendSurfer: better viral topics grid and keyword builder
- Polished BrandableForge: refined mode selectors and synthesis config
- Standardized all borders, backgrounds, and spacing for Terminal v1.0
- All UI text in English with enhanced tracking and monospaced typography
This commit is contained in:
2025-12-18 09:30:50 +01:00
parent 4c08c92780
commit 460074d01f
5 changed files with 847 additions and 867 deletions

View File

@ -1,9 +1,9 @@
'use client'
import { useEffect, useState, useMemo, useCallback } from 'react'
import { useStore } from '@/lib/store'
import { api } from '@/lib/api'
import { useAnalyzePanelStore } from '@/lib/analyze-store'
import { useStore } from '@/lib/store'
import {
ExternalLink,
Loader2,
@ -14,7 +14,6 @@ import {
ChevronUp,
ChevronDown,
RefreshCw,
Clock,
Search,
Eye,
EyeOff,
@ -23,6 +22,7 @@ import {
X,
Filter,
Shield,
Gavel,
} from 'lucide-react'
import clsx from 'clsx'
@ -109,6 +109,8 @@ interface AuctionsTabProps {
export function AuctionsTab({ showToast }: AuctionsTabProps) {
const openAnalyze = useAnalyzePanelStore((s) => s.open)
const addDomain = useStore((s) => s.addDomain)
const deleteDomain = useStore((s) => s.deleteDomain)
const [items, setItems] = useState<MarketItem[]>([])
const [loading, setLoading] = useState(true)
@ -202,7 +204,7 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
const result = await api.getDomains(1, 100)
const domainToDelete = result.domains.find((d: any) => d.name === domain)
if (domainToDelete) {
await api.deleteDomain(domainToDelete.id)
await deleteDomain(domainToDelete.id)
setTrackedDomains((prev) => {
const next = new Set(Array.from(prev))
next.delete(domain)
@ -211,7 +213,7 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
showToast(`Removed: ${domain}`, 'success')
}
} else {
await api.addDomain(domain)
await addDomain(domain)
setTrackedDomains((prev) => new Set([...Array.from(prev), domain]))
showToast(`Tracking: ${domain}`, 'success')
}
@ -220,7 +222,7 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
} finally {
setTrackingInProgress(null)
}
}, [trackedDomains, trackingInProgress, showToast])
}, [trackedDomains, trackingInProgress, showToast, addDomain, deleteDomain])
const filteredItems = useMemo(() => {
let filtered = items
@ -285,8 +287,28 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
return (
<div className="space-y-4">
{/* Search & Filters */}
<div className="space-y-3">
{/* Header Stats */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-accent/10 border border-accent/20 flex items-center justify-center">
<Gavel className="w-5 h-5 text-accent" />
</div>
<div>
<div className="text-xl font-bold text-white font-mono">
{stats.total.toLocaleString()}
</div>
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Live auctions & fixed price</div>
</div>
</div>
<button
onClick={handleRefresh}
disabled={refreshing}
className="p-2 border border-white/10 text-white/30 hover:text-white hover:bg-white/5 transition-colors"
>
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
</button>
</div>
{/* Search */}
<div className={clsx(
"relative border transition-all duration-200",
@ -300,7 +322,7 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
onChange={(e) => setSearchQuery(e.target.value)}
onFocus={() => setSearchFocused(true)}
onBlur={() => setSearchFocused(false)}
placeholder="Filter auctions..."
placeholder="Search auctions..."
className="flex-1 bg-transparent px-3 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono"
/>
{searchQuery && (
@ -308,9 +330,6 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
<X className="w-4 h-4" />
</button>
)}
<button onClick={handleRefresh} disabled={refreshing} className="p-3 text-white/30 hover:text-white transition-colors">
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
</button>
</div>
</div>
@ -318,24 +337,25 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
<button
onClick={() => setFiltersOpen(!filtersOpen)}
className={clsx(
"flex items-center justify-between w-full py-2 px-3 border transition-colors",
"flex items-center justify-between w-full py-2.5 px-4 border transition-colors",
filtersOpen ? "border-accent/30 bg-accent/[0.05]" : "border-white/[0.08] bg-white/[0.02]"
)}
>
<div className="flex items-center gap-2">
<Filter className="w-4 h-4 text-white/40" />
<span className="text-xs font-mono text-white/60">Filters</span>
{activeFiltersCount > 0 && <span className="px-1.5 py-0.5 text-[9px] font-bold bg-accent text-black">{activeFiltersCount}</span>}
<span className="text-xs font-mono text-white/60 uppercase tracking-widest">Market Filters</span>
{activeFiltersCount > 0 && <span className="px-1.5 py-0.5 text-[9px] font-bold bg-accent text-black ml-1">{activeFiltersCount}</span>}
</div>
<ChevronRight className={clsx("w-4 h-4 text-white/30 transition-transform", filtersOpen && "rotate-90")} />
</button>
{/* Filters Panel */}
{filtersOpen && (
<div className="p-3 border border-white/[0.08] bg-white/[0.02] space-y-3 animate-in fade-in slide-in-from-top-2 duration-200">
<div className="p-4 border border-white/[0.08] bg-white/[0.02] space-y-4 animate-in fade-in slide-in-from-top-2 duration-200">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Source */}
<div>
<div className="text-[9px] font-mono text-white/30 uppercase tracking-wider mb-2">Source</div>
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Source</div>
<div className="flex gap-2">
{[
{ value: 'all', label: 'All' },
@ -346,8 +366,8 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
key={item.value}
onClick={() => setSourceFilter(item.value as SourceFilter)}
className={clsx(
"flex-1 py-2 text-[10px] font-bold uppercase tracking-wider border transition-colors flex items-center justify-center gap-1",
sourceFilter === item.value ? "border-accent bg-accent/10 text-accent" : "border-white/[0.08] text-white/40"
"flex-1 py-2 text-[10px] font-bold uppercase tracking-wider border transition-colors flex items-center justify-center gap-1.5",
sourceFilter === item.value ? "border-accent bg-accent/10 text-accent font-bold" : "border-white/[0.08] text-white/40"
)}
>
{item.icon && <item.icon className="w-3 h-3" />}
@ -359,15 +379,15 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
{/* TLD */}
<div>
<div className="text-[9px] font-mono text-white/30 uppercase tracking-wider mb-2">TLD</div>
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Quick TLD</div>
<div className="flex gap-2 flex-wrap">
{['all', 'com', 'ai', 'io', 'net'].map((tld) => (
<button
key={tld}
onClick={() => setTldFilter(tld)}
className={clsx(
"px-3 py-1.5 text-[10px] font-mono uppercase border transition-colors",
tldFilter === tld ? "border-accent bg-accent/10 text-accent" : "border-white/[0.08] text-white/40"
"px-3 py-2 text-[10px] font-mono uppercase border transition-colors",
tldFilter === tld ? "border-accent bg-accent/10 text-accent font-bold" : "border-white/[0.08] text-white/40"
)}
>
{tld === 'all' ? 'All' : `.${tld}`}
@ -375,10 +395,12 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
))}
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Price */}
<div>
<div className="text-[9px] font-mono text-white/30 uppercase tracking-wider mb-2">Price</div>
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Price Range</div>
<div className="flex gap-2">
{[
{ value: 'all', label: 'All' },
@ -390,8 +412,8 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
key={item.value}
onClick={() => setPriceRange(item.value as PriceRange)}
className={clsx(
"flex-1 py-1.5 text-[10px] font-mono border transition-colors",
priceRange === item.value ? "border-amber-400 bg-amber-400/10 text-amber-400" : "border-white/[0.08] text-white/40"
"flex-1 py-2 text-[10px] font-mono border transition-colors",
priceRange === item.value ? "border-amber-400 bg-amber-400/10 text-amber-400 font-bold" : "border-white/[0.08] text-white/40"
)}
>
{item.label}
@ -401,62 +423,74 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
</div>
{/* Spam Filter */}
<div>
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Visibility</div>
<button
onClick={() => setHideSpam(!hideSpam)}
className={clsx(
"flex items-center justify-between w-full py-2 px-3 border transition-colors",
hideSpam ? "border-white/20 bg-white/[0.05]" : "border-white/[0.08]"
"flex items-center justify-between w-full py-2 px-4 border transition-colors",
hideSpam ? "border-accent/30 bg-accent/5" : "border-white/[0.08]"
)}
>
<div className="flex items-center gap-2">
<Ban className="w-4 h-4 text-white/40" />
<span className="text-xs font-mono text-white/60">Hide spam domains</span>
<div className="flex items-center gap-2.5">
<Ban className={clsx("w-3.5 h-3.5", hideSpam ? "text-accent" : "text-white/30")} />
<span className={clsx("text-[10px] font-mono uppercase tracking-wider", hideSpam ? "text-accent" : "text-white/50")}>Hide Spam Domains</span>
</div>
<div className={clsx("w-4 h-4 border flex items-center justify-center", hideSpam ? "border-accent bg-accent" : "border-white/30")}>
{hideSpam && <span className="text-black text-[10px] font-bold"></span>}
<div className={clsx("w-3.5 h-3.5 border flex items-center justify-center", hideSpam ? "border-accent bg-accent" : "border-white/20")}>
{hideSpam && <span className="text-black text-[8px] font-bold"></span>}
</div>
</button>
</div>
</div>
</div>
)}
{/* Results Header */}
<div className="flex items-center justify-between px-1 text-[10px] font-mono text-white/30 uppercase tracking-[0.1em]">
<div className="flex items-center gap-2">
<div className="w-1 h-1 bg-accent rounded-full animate-pulse" />
<span>{filteredItems.length} active listings found</span>
</div>
<div className="flex items-center gap-3">
<span className="text-accent">{stats.highScore} premium assets</span>
{totalPages > 1 && <span>Page {page} / {totalPages}</span>}
</div>
</div>
{/* Stats Bar */}
<div className="flex items-center justify-between text-[10px] font-mono text-white/40">
<span>{filteredItems.length} domains shown</span>
<span>{filteredItems.filter((i) => i.pounce_score >= 80).length} high score</span>
</div>
{/* Results */}
{/* Results Table */}
{filteredItems.length === 0 ? (
<div className="text-center py-16 border border-dashed border-white/[0.08]">
<Search className="w-8 h-8 text-white/10 mx-auto mb-3" />
<p className="text-white/40 text-sm font-mono">No domains found</p>
<p className="text-white/25 text-xs font-mono mt-1">Try adjusting filters</p>
<div className="text-center py-24 border border-dashed border-white/[0.08] bg-white/[0.01]">
<Search className="w-12 h-12 text-white/5 mx-auto mb-4" />
<p className="text-white/40 text-sm font-mono uppercase tracking-widest font-bold">No assets found</p>
<p className="text-white/20 text-[10px] font-mono mt-3 uppercase tracking-wider max-w-xs mx-auto leading-relaxed">
Try adjusting your search criteria or filters
</p>
</div>
) : (
<>
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
<div className="border border-white/[0.08] bg-white/[0.01] overflow-hidden">
{/* Desktop Table Header */}
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_80px_120px] gap-4 px-3 py-2 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
<button onClick={() => handleSort('domain')} className="flex items-center gap-1 hover:text-white/60 text-left" title="Domain name being auctioned">
Domain
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_100px_140px] gap-4 px-5 py-3 text-[10px] font-mono text-white/30 uppercase tracking-[0.2em] border-b border-white/[0.08] bg-white/[0.02]">
<button onClick={() => handleSort('domain')} className="flex items-center gap-2 hover:text-white transition-colors text-left group">
<span className={clsx(sortField === 'domain' && "text-accent font-bold")}>Domain Asset</span>
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
</button>
<button onClick={() => handleSort('score')} className="flex items-center gap-1 justify-center hover:text-white/60" title="Pounce Score - our AI-powered quality rating (0-100)">
Score
{sortField === 'score' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
<button onClick={() => handleSort('score')} className="flex items-center gap-2 justify-center hover:text-white transition-colors group">
<span className={clsx(sortField === 'score' && "text-accent font-bold")}>Score</span>
{sortField === 'score' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
</button>
<button onClick={() => handleSort('price')} className="flex items-center gap-1 justify-end hover:text-white/60" title="Current bid or buy-now price">
Price
{sortField === 'price' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
<button onClick={() => handleSort('price')} className="flex items-center gap-2 justify-end hover:text-white transition-colors group pr-4">
<span className={clsx(sortField === 'price' && "text-accent font-bold")}>Price</span>
{sortField === 'price' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
</button>
<button onClick={() => handleSort('time')} className="flex items-center gap-1 justify-center hover:text-white/60" title="Time remaining until auction ends">
Time
{sortField === 'time' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
<button onClick={() => handleSort('time')} className="flex items-center gap-2 justify-center hover:text-white transition-colors group">
<span className={clsx(sortField === 'time' && "text-accent font-bold")}>Time</span>
{sortField === 'time' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
</button>
<div className="text-right" title="Available actions: Analyze, Track, Buy">Actions</div>
<div className="text-right pr-2">Actions</div>
</div>
<div className="divide-y divide-white/[0.04]">
{filteredItems.map((item) => {
const timeLeftSec = getSecondsUntilEnd(item.end_time)
const isUrgent = timeLeftSec > 0 && timeLeftSec < 3600
@ -466,27 +500,28 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
const isTracking = trackingInProgress === item.domain
return (
<div key={item.id} className={clsx("bg-[#020202] hover:bg-white/[0.02] transition-all", isPounce && "bg-accent/[0.02]")}>
<div key={item.id} className={clsx("bg-[#020202] hover:bg-white/[0.02] transition-all group", isPounce && "bg-accent/[0.01]")}>
{/* Mobile Row */}
<div className="lg:hidden p-3">
<div className="flex items-start justify-between gap-3 mb-2">
<div className="flex items-center gap-3 min-w-0 flex-1">
<div className="min-w-0 flex-1">
<button onClick={() => openAnalyze(item.domain)} className="text-sm font-bold text-white font-mono truncate text-left">
<div className="lg:hidden p-4">
<div className="flex items-start justify-between gap-3 mb-4">
<div className="flex flex-col min-w-0">
<button onClick={() => openAnalyze(item.domain)} className="text-base font-bold text-white font-mono truncate text-left tracking-tight">
{item.domain}
</button>
<div className="flex items-center gap-2 text-[10px] font-mono text-white/30">
<span className="uppercase">{item.source}</span>
<div className="flex items-center gap-2 mt-1.5 text-[9px] font-mono text-white/30 uppercase tracking-wider">
<span className="bg-white/5 px-1.5 py-0.5 border border-white/5">{item.source}</span>
<span className="text-white/10">|</span>
<span className={clsx(isUrgent && "text-orange-400")}>{isPounce ? 'Instant' : displayTime || 'N/A'}</span>
</div>
<span className={clsx(isUrgent && "text-orange-400 font-bold")}>{isPounce ? 'INSTANT' : displayTime || 'N/A'}</span>
</div>
</div>
<div className="text-right shrink-0">
<div className={clsx("text-base font-bold font-mono", isPounce ? "text-accent" : "text-white")}>{formatPrice(item.price)}</div>
<div className={clsx("text-[9px] font-mono px-1 py-0.5 inline-block", item.pounce_score >= 80 ? "text-accent bg-accent/10" : item.pounce_score >= 50 ? "text-amber-400 bg-amber-400/10" : "text-white/30 bg-white/5")}>
Score {item.pounce_score}
<div className={clsx("text-base font-bold font-mono tracking-tight", isPounce ? "text-accent" : "text-white")}>{formatPrice(item.price)}</div>
<div className={clsx(
"text-[9px] font-mono px-1.5 py-0.5 mt-1 inline-block border",
item.pounce_score >= 80 ? "text-accent bg-accent/5 border-accent/20" : item.pounce_score >= 50 ? "text-amber-400 bg-amber-400/5 border-amber-400/20" : "text-white/30 bg-white/5 border-white/5"
)}>
SCORE: {item.pounce_score}
</div>
</div>
</div>
@ -496,104 +531,91 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
onClick={() => handleTrack(item.domain)}
disabled={isTracking}
className={clsx(
"flex-1 py-2 text-[10px] font-bold uppercase tracking-wider border flex items-center justify-center gap-1.5 transition-all",
isTracked ? "border-accent bg-accent/10 text-accent" : "border-white/[0.08] text-white/40"
"flex-1 h-10 text-[10px] font-bold uppercase tracking-widest border flex items-center justify-center gap-2 transition-all",
isTracked ? "border-accent bg-accent/5 text-accent" : "border-white/10 text-white/40 hover:bg-white/5"
)}
>
{isTracking ? <Loader2 className="w-3 h-3 animate-spin" /> : isTracked ? <Eye className="w-3 h-3" /> : <EyeOff className="w-3 h-3" />}
{isTracking ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : isTracked ? <Eye className="w-4 h-4" /> : <EyeOff className="w-4 h-4" />}
{isTracked ? 'Tracked' : 'Track'}
</button>
<button onClick={() => openAnalyze(item.domain)} className="w-10 py-2 text-[10px] font-bold uppercase tracking-wider border border-white/[0.08] text-white/50 flex items-center justify-center transition-all hover:text-white hover:bg-white/5">
<Shield className="w-3.5 h-3.5" />
<button onClick={() => openAnalyze(item.domain)} className="w-12 h-10 border border-white/10 text-white/40 flex items-center justify-center hover:text-accent hover:border-accent/20 hover:bg-accent/5 transition-all">
<Shield className="w-4.5 h-4.5" />
</button>
<a
href={item.url}
target={isPounce ? '_self' : '_blank'}
rel={isPounce ? undefined : 'noopener noreferrer'}
className={clsx("flex-1 py-2 text-[10px] font-bold uppercase tracking-wider flex items-center justify-center gap-1.5 transition-all", isPounce ? "bg-accent text-black" : "bg-white/10 text-white")}
className={clsx("flex-1 h-10 text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-1.5 transition-all", isPounce ? "bg-accent text-black" : "bg-white/10 text-white")}
>
{isPounce ? 'Buy' : 'Bid'}
{!isPounce && <ExternalLink className="w-3 h-3" />}
{!isPounce && <ExternalLink className="w-3.5 h-3.5" />}
</a>
</div>
</div>
{/* Desktop Row */}
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_80px_120px] gap-4 items-center p-4 group">
<div className="flex items-center gap-3 min-w-0 flex-1">
<div className="min-w-0">
<button onClick={() => openAnalyze(item.domain)} className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left">
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_100px_140px] gap-4 items-center px-5 py-3.5">
<div className="flex items-center gap-3 min-w-0">
<button onClick={() => openAnalyze(item.domain)} className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left tracking-tight">
{item.domain}
</button>
<div className="flex items-center gap-2 text-[10px] font-mono text-white/30">
<span className="uppercase">{item.source}</span>
{isPounce && item.verified && (
<>
<span className="text-white/10">|</span>
<span className="text-accent flex items-center gap-0.5">
<ShieldCheck className="w-3 h-3" />
Verified
</span>
</>
)}
{item.num_bids ? (
<>
<span className="text-white/10">|</span>
{item.num_bids} bids
</>
) : null}
</div>
<div className="flex items-center gap-2 text-[9px] font-mono text-white/20 uppercase tracking-widest opacity-0 group-hover:opacity-100 transition-opacity">
<span>{item.source}</span>
{isPounce && item.verified && <ShieldCheck className="w-3 h-3 text-accent" />}
</div>
</div>
<div className="w-16 text-center shrink-0">
<span className={clsx("text-xs font-mono font-bold px-2 py-0.5", item.pounce_score >= 80 ? "text-accent bg-accent/10" : item.pounce_score >= 50 ? "text-amber-400 bg-amber-400/10" : "text-white/40 bg-white/5")}>
<div className="text-center">
<span className={clsx(
"text-[10px] font-mono font-bold px-2 py-0.5 border",
item.pounce_score >= 80 ? "text-accent bg-accent/5 border-accent/20" : item.pounce_score >= 50 ? "text-amber-400 bg-amber-400/5 border-amber-400/20" : "text-white/30 bg-white/5 border-white/5"
)}>
{item.pounce_score}
</span>
</div>
<div className="w-24 text-right shrink-0">
<div className={clsx("font-mono text-sm font-bold", isPounce ? "text-accent" : "text-white")}>{formatPrice(item.price)}</div>
<div className="text-[9px] font-mono text-white/30 uppercase">{item.price_type === 'bid' ? 'Bid' : 'Buy Now'}</div>
<div className="text-right pr-4">
<div className={clsx("font-mono text-sm font-bold tracking-tight", isPounce ? "text-accent" : "text-white")}>{formatPrice(item.price)}</div>
<div className="text-[9px] font-mono text-white/20 uppercase tracking-widest">{item.price_type === 'bid' ? 'BID' : 'BUY NOW'}</div>
</div>
<div className="w-20 text-center shrink-0">
<div className="text-center">
{isPounce ? (
<span className="text-xs text-accent font-mono flex items-center justify-center gap-1">
<span className="text-[10px] text-accent font-mono font-bold flex items-center justify-center gap-1 uppercase tracking-widest">
<Zap className="w-3 h-3" />
Instant
</span>
) : (
<span className={clsx("text-xs font-mono", isUrgent ? "text-orange-400" : "text-white/50")}>{displayTime || 'N/A'}</span>
<span className={clsx("text-[10px] font-mono uppercase tracking-widest", isUrgent ? "text-orange-400 font-bold" : "text-white/40")}>{displayTime || 'N/A'}</span>
)}
</div>
<div className="flex items-center gap-2 shrink-0 opacity-50 group-hover:opacity-100 transition-opacity">
<div className="flex items-center justify-end gap-1.5 opacity-0 group-hover:opacity-100 transition-all duration-300 transform translate-x-2 group-hover:translate-x-0">
<button
onClick={() => handleTrack(item.domain)}
disabled={isTracking}
className={clsx(
"w-7 h-7 flex items-center justify-center border transition-colors",
isTracked ? "bg-accent/10 text-accent border-accent/20 hover:bg-red-500/10 hover:text-red-400 hover:border-red-500/20" : "text-white/30 border-white/10 hover:text-accent hover:bg-accent/10 hover:border-accent/20"
"w-8.5 h-8.5 flex items-center justify-center border transition-all",
isTracked ? "bg-accent/5 text-accent border-accent/20 hover:bg-red-500/5 hover:text-red-400 hover:border-red-500/20" : "text-white/30 border-white/10 hover:text-white hover:bg-white/5"
)}
title={isTracked ? "Untrack" : "Track Domain"}
>
{isTracking ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : isTracked ? <Eye className="w-3.5 h-3.5" /> : <EyeOff className="w-3.5 h-3.5" />}
{isTracking ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : isTracked ? <Eye className="w-4 h-4" /> : <EyeOff className="w-4 h-4" />}
</button>
<button onClick={() => openAnalyze(item.domain)} className="w-7 h-7 flex items-center justify-center border transition-colors text-white/30 border-white/10 hover:text-accent hover:bg-accent/10 hover:border-accent/20">
<Shield className="w-3.5 h-3.5" />
<button onClick={() => openAnalyze(item.domain)} className="w-8.5 h-8.5 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/20 hover:bg-accent/5 transition-all" title="Deep Analysis">
<Shield className="w-4 h-4" />
</button>
<a
href={item.url}
target={isPounce ? '_self' : '_blank'}
rel={isPounce ? undefined : 'noopener noreferrer'}
className={clsx("h-7 px-3 flex items-center gap-1.5 text-xs font-bold transition-colors", isPounce ? "bg-accent text-black hover:bg-white" : "bg-white/10 text-white hover:bg-white/20")}
className={clsx("h-8.5 px-4 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest transition-all shadow-[0_0_15px_-5px_rgba(34,211,126,0.4)]", isPounce ? "bg-accent text-black hover:bg-white" : "bg-white/10 text-white hover:bg-white/20")}
>
{isPounce ? 'Buy' : 'Bid'}
{!isPounce && <ExternalLink className="w-3 h-3" />}
</a>
</div>
</div>
@ -601,35 +623,31 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
)
})}
</div>
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="flex items-center justify-between pt-4">
<div className="text-[10px] text-white/40 font-mono uppercase tracking-wider">
Page {page}/{totalPages}
</div>
<div className="flex items-center gap-1">
<div className="flex items-center justify-center gap-1 pt-6">
<button
onClick={() => handlePageChange(page - 1)}
disabled={page === 1}
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-20 disabled:cursor-not-allowed transition-all"
>
<ChevronLeft className="w-4 h-4" />
<ChevronLeft className="w-5 h-5" />
</button>
<span className="text-xs text-white/50 font-mono px-2">
{page}/{totalPages}
<div className="flex items-center bg-white/[0.02] border border-white/[0.08] px-5 h-10">
<span className="text-[11px] text-white/40 font-mono uppercase tracking-widest">
Page <span className="text-white font-bold">{page}</span> / {totalPages}
</span>
</div>
<button
onClick={() => handlePageChange(page + 1)}
disabled={page === totalPages}
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-20 disabled:cursor-not-allowed transition-all"
>
<ChevronRight className="w-4 h-4" />
<ChevronRight className="w-5 h-5" />
</button>
</div>
</div>
)}
</>
)}

View File

@ -16,6 +16,8 @@ import {
RefreshCw,
Brain,
Dices,
ChevronRight,
X,
} from 'lucide-react'
import Link from 'next/link'
import { api } from '@/lib/api'
@ -71,7 +73,7 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
try {
const res = await api.huntBrandables({ pattern, tlds, limit: 24, max_checks: 300 })
setResults(res.items.map(i => ({ domain: i.domain })))
showToast(`Found ${res.items.length} domains!`, 'success')
showToast(`Generated ${res.items.length} assets!`, 'success')
} catch (e) {
showToast('Generation failed', 'error')
} finally {
@ -90,7 +92,7 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
const checkRes = await api.huntKeywords({ keywords: res.names, tlds })
const available = checkRes.items.filter(i => i.status === 'available')
setResults(available.map(i => ({ domain: i.domain })))
showToast(`Found ${available.length} available from ${res.names.length} AI names!`, 'success')
showToast(`Found ${available.length} free domains via AI!`, 'success')
}
} catch (e) {
showToast('AI generation failed', 'error')
@ -118,7 +120,7 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
await addDomain(domain)
showToast(`Added: ${domain}`, 'success')
} catch (e) {
showToast('Failed', 'error')
showToast('Failed to track', 'error')
} finally {
setTracking(null)
}
@ -128,52 +130,42 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
return (
<div className="space-y-6">
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* HEADER */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<div className="pb-4 border-b border-white/10">
<h2 className="text-xl font-bold text-white flex items-center gap-2">
<Wand2 className="w-5 h-5 text-purple-400" />
Brandable Forge
</h2>
<p className="text-sm text-white/60 mt-1">
Generate unique, memorable domain names
</p>
{/* Header */}
<div className="flex items-center gap-4 pb-5 border-b border-white/[0.08]">
<div className="w-12 h-12 bg-purple-500/10 border border-purple-500/20 flex items-center justify-center shrink-0">
<Wand2 className="w-6 h-6 text-purple-400" />
</div>
<div>
<h2 className="text-xl font-bold text-white font-mono tracking-tight">Brandable Forge</h2>
<p className="text-[10px] font-mono text-white/40 uppercase tracking-widest mt-1">Synthesize unique, high-value naming assets</p>
</div>
</div>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* MODE SELECTOR - BIG CLEAR BUTTONS */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<div className="grid grid-cols-2 gap-3">
{/* Mode Selector */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{/* Pattern Mode */}
<button
onClick={() => setMode('pattern')}
className={clsx(
"p-4 sm:p-5 border-2 transition-all text-left",
"relative p-5 border transition-all duration-300 text-left group overflow-hidden",
mode === 'pattern'
? "border-accent bg-accent/10"
: "border-white/10 bg-white/[0.02] hover:border-white/20"
? "border-accent bg-accent/5 shadow-[0_0_30px_-10px_rgba(34,211,126,0.2)]"
: "border-white/[0.08] bg-white/[0.01] hover:border-white/20 hover:bg-white/[0.03]"
)}
>
<div className="flex items-center gap-3 mb-2">
<div className="flex items-center gap-4 relative z-10">
<div className={clsx(
"w-10 h-10 flex items-center justify-center",
mode === 'pattern' ? "bg-accent/20" : "bg-white/5"
"w-12 h-12 flex items-center justify-center border transition-colors",
mode === 'pattern' ? "border-accent/30 bg-accent/10" : "border-white/10 bg-white/5"
)}>
<Dices className={clsx("w-5 h-5", mode === 'pattern' ? "text-accent" : "text-white/50")} />
<Dices className={clsx("w-6 h-6", mode === 'pattern' ? "text-accent" : "text-white/30")} />
</div>
<div>
<p className={clsx("font-bold", mode === 'pattern' ? "text-accent" : "text-white")}>
Pattern
</p>
<p className={clsx("text-xs", mode === 'pattern' ? "text-accent/70" : "text-white/50")}>
CVCVC, CVCCV, Human
</p>
<p className={clsx("font-bold font-mono text-sm tracking-tight", mode === 'pattern' ? "text-accent" : "text-white/70")}>Pattern Engine</p>
<p className="text-[10px] font-mono text-white/30 uppercase tracking-widest mt-0.5">Classic Brandables</p>
</div>
</div>
<p className="text-xs text-white/40 hidden sm:block">
Generate random names using proven naming patterns
</p>
{mode === 'pattern' && <div className="absolute top-0 right-0 w-2 h-2 bg-accent" />}
</button>
{/* AI Mode */}
@ -181,135 +173,83 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
onClick={() => hasAI && setMode('ai')}
disabled={!hasAI}
className={clsx(
"p-4 sm:p-5 border-2 transition-all text-left relative",
!hasAI && "opacity-60",
"relative p-5 border transition-all duration-300 text-left group overflow-hidden",
!hasAI && "opacity-50 grayscale",
mode === 'ai'
? "border-purple-500 bg-purple-500/10"
: "border-white/10 bg-white/[0.02] hover:border-white/20"
? "border-purple-500 bg-purple-500/5 shadow-[0_0_30px_-10px_rgba(168,85,247,0.2)]"
: "border-white/[0.08] bg-white/[0.01] hover:border-white/20 hover:bg-white/[0.03]"
)}
>
{!hasAI && (
<div className="absolute top-2 right-2">
<Lock className="w-4 h-4 text-white/30" />
</div>
)}
<div className="flex items-center gap-3 mb-2">
{!hasAI && <div className="absolute top-2 right-2"><Lock className="w-3 h-3 text-white/20" /></div>}
<div className="flex items-center gap-4 relative z-10">
<div className={clsx(
"w-10 h-10 flex items-center justify-center",
mode === 'ai' ? "bg-purple-500/20" : "bg-white/5"
"w-12 h-12 flex items-center justify-center border transition-colors",
mode === 'ai' ? "border-purple-500/30 bg-purple-500/10" : "border-white/10 bg-white/5"
)}>
<Brain className={clsx("w-5 h-5", mode === 'ai' ? "text-purple-400" : "text-white/50")} />
<Brain className={clsx("w-6 h-6", mode === 'ai' ? "text-purple-400" : "text-white/30")} />
</div>
<div>
<p className={clsx("font-bold", mode === 'ai' ? "text-purple-400" : "text-white")}>
AI Concept
</p>
<p className={clsx("text-xs", mode === 'ai' ? "text-purple-400/70" : "text-white/50")}>
{hasAI ? 'Describe your brand' : 'Trader+ only'}
</p>
<p className={clsx("font-bold font-mono text-sm tracking-tight", mode === 'ai' ? "text-purple-400" : "text-white/70")}>Vision Core AI</p>
<p className="text-[10px] font-mono text-white/30 uppercase tracking-widest mt-0.5">Concept to Name</p>
</div>
</div>
<p className="text-xs text-white/40 hidden sm:block">
{hasAI ? 'AI generates names based on your concept' : 'Upgrade to unlock AI naming'}
</p>
{mode === 'ai' && <div className="absolute top-0 right-0 w-2 h-2 bg-purple-500" />}
</button>
</div>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* PATTERN MODE */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{mode === 'pattern' && (
<div className="border border-white/10 bg-white/[0.02] p-4 sm:p-5 space-y-5">
{/* Pattern Selection */}
<div>
<p className="text-xs text-white/60 font-mono uppercase tracking-wider mb-3">Choose Pattern</p>
{/* Config Panel */}
<div className={clsx(
"border overflow-hidden transition-all duration-500",
mode === 'ai' ? "border-purple-500/30 bg-purple-500/[0.01]" : "border-white/[0.08] bg-white/[0.01]"
)}>
<div className="px-5 py-4 border-b border-white/[0.08] bg-white/[0.01] flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-[10px] font-mono text-white/30 uppercase tracking-[0.2em]">Synthesis Configuration</span>
</div>
<div className="flex items-center gap-2">
<div className={clsx("w-1 h-1 rounded-full", mode === 'ai' ? "bg-purple-500 animate-pulse" : "bg-accent")} />
<span className="text-[10px] font-mono text-white/20 uppercase tracking-widest">{mode.toUpperCase()} MODE</span>
</div>
</div>
<div className="p-5 space-y-6">
{mode === 'pattern' ? (
<div className="space-y-4">
<span className="text-[10px] font-mono text-white/30 uppercase tracking-widest">Select Linguistic Pattern</span>
<div className="grid grid-cols-3 gap-2">
{PATTERNS.map(p => (
<button
key={p.key}
onClick={() => setPattern(p.key)}
className={clsx(
"p-3 border text-center transition-all",
"p-3 border transition-all duration-300 group",
pattern === p.key
? "border-accent bg-accent/10"
: "border-white/10 hover:border-white/20"
: "border-white/10 hover:border-white/20 hover:bg-white/5"
)}
>
<p className={clsx(
"text-sm font-bold font-mono",
pattern === p.key ? "text-accent" : "text-white"
)}>
{p.label}
</p>
<p className="text-[10px] text-white/50 mt-0.5">{p.example}</p>
<p className={clsx("text-xs font-black font-mono tracking-widest", pattern === p.key ? "text-accent" : "text-white/40 group-hover:text-white/60")}>{p.label}</p>
<p className="text-[9px] font-mono text-white/20 mt-1 uppercase tracking-tighter">{p.desc}</p>
</button>
))}
</div>
</div>
{/* TLDs */}
<div>
<p className="text-xs text-white/60 font-mono uppercase tracking-wider mb-3">Select TLDs</p>
<div className="flex flex-wrap gap-2">
{TLDS.map(tld => (
<button
key={tld}
onClick={() => setTlds(prev =>
prev.includes(tld) ? prev.filter(t => t !== tld) : [...prev, tld]
)}
className={clsx(
"px-3 py-2 text-sm font-mono border transition-all",
tlds.includes(tld)
? "border-accent bg-accent/10 text-accent"
: "border-white/10 text-white/50 hover:text-white"
)}
>
.{tld}
</button>
))}
</div>
</div>
{/* Generate */}
<button
onClick={generatePattern}
disabled={isGenerating || tlds.length === 0}
className={clsx(
"w-full py-4 text-sm font-bold uppercase tracking-wider flex items-center justify-center gap-2 transition-all",
isGenerating || tlds.length === 0
? "bg-white/10 text-white/40"
: "bg-accent text-black hover:bg-white"
)}
>
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4" />}
Generate {pattern.toUpperCase()} Names
</button>
</div>
)}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* AI MODE */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{mode === 'ai' && hasAI && (
<div className="border border-purple-500/30 bg-purple-500/5 p-4 sm:p-5 space-y-5">
{/* Concept Input */}
<div>
<p className="text-xs text-purple-300 font-mono uppercase tracking-wider mb-3">
Describe Your Brand Concept
</p>
) : (
<div className="space-y-4">
<span className="text-[10px] font-mono text-purple-300/40 uppercase tracking-widest">Describe Your Identity Concept</span>
<textarea
value={concept}
onChange={(e) => setConcept(e.target.value)}
placeholder="e.g., AI startup for legal documents, crypto wallet for teens, fitness tracking app..."
placeholder="e.g., A minimalist AI agent for legal risk management..."
rows={3}
className="w-full px-4 py-3 bg-white/5 border border-purple-500/30 text-white placeholder:text-white/30 outline-none focus:border-purple-400 resize-none font-mono text-sm"
className="w-full px-4 py-3 bg-white/[0.03] border border-purple-500/20 text-sm font-mono text-white placeholder:text-white/10 outline-none focus:border-purple-500/50 resize-none transition-all"
/>
</div>
)}
{/* TLDs */}
<div>
<p className="text-xs text-purple-300 font-mono uppercase tracking-wider mb-3">Select TLDs</p>
<div className="flex flex-wrap gap-2">
<div className="space-y-4">
<span className="text-[10px] font-mono text-white/30 uppercase tracking-widest">Target Extensions</span>
<div className="flex flex-wrap gap-1.5">
{TLDS.map(tld => (
<button
key={tld}
@ -317,10 +257,10 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
prev.includes(tld) ? prev.filter(t => t !== tld) : [...prev, tld]
)}
className={clsx(
"px-3 py-2 text-sm font-mono border transition-all",
"px-3 py-1.5 text-[10px] font-mono border transition-all uppercase tracking-widest",
tlds.includes(tld)
? "border-purple-400 bg-purple-500/20 text-purple-300"
: "border-white/10 text-white/50 hover:text-white"
? mode === 'ai' ? "border-purple-500 bg-purple-500/10 text-purple-300 font-black" : "border-accent bg-accent/10 text-accent font-black"
: "border-white/10 text-white/40 hover:text-white hover:bg-white/5"
)}
>
.{tld}
@ -329,65 +269,52 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
</div>
</div>
{/* Generate */}
<button
onClick={generateFromConcept}
disabled={aiLoading || !concept.trim() || tlds.length === 0}
onClick={mode === 'pattern' ? generatePattern : generateFromConcept}
disabled={isGenerating || (mode === 'ai' && !concept.trim()) || tlds.length === 0}
className={clsx(
"w-full py-4 text-sm font-bold uppercase tracking-wider flex items-center justify-center gap-2 transition-all",
aiLoading || !concept.trim() || tlds.length === 0
? "bg-white/10 text-white/40"
: "bg-purple-500 text-white hover:bg-purple-400"
"w-full py-4 text-[11px] font-black uppercase tracking-[0.2em] flex items-center justify-center gap-3 transition-all",
isGenerating || (mode === 'ai' && !concept.trim()) || tlds.length === 0
? "bg-white/5 text-white/20 border border-white/5"
: mode === 'ai'
? "bg-purple-600 text-white hover:bg-purple-500 shadow-[0_0_30px_-10px_rgba(168,85,247,0.4)]"
: "bg-accent text-black hover:bg-white shadow-[0_0_30px_-10px_rgba(34,211,126,0.4)]"
)}
>
{aiLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Brain className="w-4 h-4" />}
Generate AI Names
{isGenerating ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4" />}
{mode === 'ai' ? 'Synthesize AI Assets' : `Forge ${pattern.toUpperCase()} Names`}
</button>
</div>
)}
</div>
{/* AI Upgrade CTA */}
{mode === 'ai' && !hasAI && (
<div className="border border-white/10 bg-white/[0.02] p-8 text-center">
<Lock className="w-10 h-10 text-white/20 mx-auto mb-3" />
<h3 className="text-lg font-bold text-white mb-2">AI Naming Requires Upgrade</h3>
<p className="text-sm text-white/50 mb-4 max-w-sm mx-auto">
Describe your brand and let AI generate unique, brandable domain names for you.
<div className="border border-white/10 bg-white/[0.01] p-10 text-center animate-in fade-in zoom-in-95 duration-500">
<Lock className="w-12 h-12 text-white/5 mx-auto mb-4" />
<h3 className="text-lg font-bold text-white font-mono tracking-tight mb-2 uppercase">Neural Forge Locked</h3>
<p className="text-sm text-white/30 font-mono mb-6 max-w-sm mx-auto uppercase tracking-wider leading-relaxed">
AI-driven naming requires Trader or Tycoon clearance
</p>
<Link
href="/pricing"
className="inline-flex items-center gap-2 px-6 py-3 bg-accent text-black text-sm font-bold uppercase hover:bg-white transition-colors"
className="inline-flex items-center gap-3 px-8 py-3 bg-accent text-black text-[11px] font-black uppercase tracking-widest hover:bg-white transition-all active:scale-95"
>
<Sparkles className="w-4 h-4" />
Upgrade to Trader
Upgrade Plan
</Link>
</div>
)}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* RESULTS */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* Results */}
{results.length > 0 && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<p className="text-sm text-white/70">
<span className="text-accent font-bold">{results.length}</span> available domains
<div className="space-y-4 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="flex items-center justify-between px-1">
<p className="text-[10px] font-mono text-white/30 uppercase tracking-[0.2em]">
<span className={clsx("font-black", mode === 'ai' ? "text-purple-400" : "text-accent")}>{results.length}</span> Synthesized naming assets available
</p>
<div className="flex gap-3">
<button
onClick={copyAll}
className="text-xs font-mono text-white/50 hover:text-accent flex items-center gap-1"
>
<Copy className="w-3 h-3" />
Copy all
</button>
<button
onClick={mode === 'pattern' ? generatePattern : generateFromConcept}
disabled={isGenerating}
className="text-xs font-mono text-white/50 hover:text-white flex items-center gap-1"
>
<RefreshCw className={clsx("w-3 h-3", isGenerating && "animate-spin")} />
Refresh
<div className="flex gap-4">
<button onClick={copyAll} className="text-[9px] font-mono text-white/20 hover:text-white uppercase tracking-widest transition-colors">
Batch Copy
</button>
</div>
</div>
@ -397,49 +324,49 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
<div
key={r.domain}
className={clsx(
"flex items-center justify-between p-3 border transition-all group",
"flex items-center justify-between p-4 border transition-all duration-300 group",
mode === 'ai'
? "bg-purple-500/5 border-purple-500/20 hover:bg-purple-500/10"
: "bg-accent/5 border-accent/20 hover:bg-accent/10"
? "bg-purple-500/[0.02] border-purple-500/20 hover:border-purple-500/40 hover:bg-purple-500/[0.04]"
: "bg-accent/[0.02] border-accent/20 hover:border-accent/40 hover:bg-accent/[0.04]"
)}
>
<div className="flex items-center gap-2.5 min-w-0">
<div className="flex items-center gap-3 min-w-0">
<span className={clsx(
"w-6 h-6 text-[10px] font-bold font-mono flex items-center justify-center shrink-0",
"text-[9px] font-bold font-mono px-1.5 py-0.5 border shrink-0",
mode === 'ai'
? "bg-purple-500/20 text-purple-300"
: "bg-accent/20 text-accent"
? "bg-purple-500/10 border-purple-500/20 text-purple-300"
: "bg-accent/10 border-accent/20 text-accent"
)}>
{idx + 1}
{(idx + 1).toString().padStart(2, '0')}
</span>
<button
onClick={() => openAnalyze(r.domain)}
className={clsx(
"text-sm font-mono font-medium text-white truncate",
"text-sm font-bold font-mono text-white truncate transition-colors",
mode === 'ai' ? "group-hover:text-purple-300" : "group-hover:text-accent"
)}
>
{r.domain}
</button>
</div>
<div className="flex items-center gap-0.5 shrink-0 opacity-60 group-hover:opacity-100">
<button onClick={() => copy(r.domain)} className="w-7 h-7 flex items-center justify-center text-white/60 hover:text-white">
{copied === r.domain ? <Check className="w-3 h-3 text-accent" /> : <Copy className="w-3 h-3" />}
<div className="flex items-center gap-1 shrink-0 opacity-0 group-hover:opacity-100 transition-all transform translate-x-2 group-hover:translate-x-0">
<button onClick={() => copy(r.domain)} className="w-7 h-7 flex items-center justify-center border border-white/10 text-white/30 hover:text-white hover:bg-white/5">
{copied === r.domain ? <Check className="w-3.5 h-3.5 text-accent" /> : <Copy className="w-3.5 h-3.5" />}
</button>
<button onClick={() => track(r.domain)} disabled={tracking === r.domain} className="w-7 h-7 flex items-center justify-center text-white/60 hover:text-white">
{tracking === r.domain ? <Loader2 className="w-3 h-3 animate-spin" /> : <Eye className="w-3 h-3" />}
<button onClick={() => track(r.domain)} disabled={tracking === r.domain} className="w-7 h-7 flex items-center justify-center border border-white/10 text-white/30 hover:text-white hover:bg-white/5">
{tracking === r.domain ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-3.5 h-3.5" />}
</button>
<button onClick={() => openAnalyze(r.domain)} className="w-7 h-7 flex items-center justify-center text-white/60 hover:text-accent">
<Shield className="w-3 h-3" />
<button onClick={() => openAnalyze(r.domain)} className="w-7 h-7 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/20 hover:bg-accent/5">
<Shield className="w-3.5 h-3.5" />
</button>
<a
href={`https://www.namecheap.com/domains/registration/results/?domain=${r.domain}`}
target="_blank"
rel="noopener noreferrer"
className={clsx(
"h-7 px-2 text-[10px] font-bold flex items-center",
"h-7 px-3 text-[10px] font-black uppercase tracking-widest flex items-center transition-all",
mode === 'ai'
? "bg-purple-500 text-white hover:bg-purple-400"
? "bg-purple-600 text-white hover:bg-purple-500"
: "bg-accent text-black hover:bg-white"
)}
>
@ -454,21 +381,20 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
{/* Empty State */}
{results.length === 0 && !isGenerating && (
<div className="text-center py-12 border border-dashed border-white/10">
<Wand2 className="w-10 h-10 text-white/20 mx-auto mb-3" />
<p className="text-white/50">
{mode === 'pattern'
? 'Choose a pattern and click Generate'
: 'Describe your brand concept'}
<div className="text-center py-24 border border-dashed border-white/[0.08] bg-white/[0.01]">
<Wand2 className="w-12 h-12 text-white/5 mx-auto mb-4" />
<p className="text-white/40 text-sm font-mono uppercase tracking-widest font-bold">Forge is currently idle</p>
<p className="text-white/20 text-[10px] font-mono mt-3 uppercase tracking-wider max-w-xs mx-auto leading-relaxed">
Select a generation mode above to synthesize available brandable assets
</p>
</div>
)}
{/* Loading State */}
{/* Loading Grid */}
{isGenerating && results.length === 0 && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
{[...Array(12)].map((_, i) => (
<div key={i} className="h-12 bg-white/5 animate-pulse" />
<div key={i} className="h-14 bg-white/[0.01] border border-white/[0.05] animate-pulse" />
))}
</div>
)}

View File

@ -5,7 +5,6 @@ import { api } from '@/lib/api'
import { useAnalyzePanelStore } from '@/lib/analyze-store'
import { useStore } from '@/lib/store'
import {
Clock,
Globe,
Loader2,
Search,
@ -165,7 +164,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
setTracking(domain)
try {
await addDomain(domain)
showToast(`Tracking ${domain}`, 'success')
showToast(`Added: ${domain}`, 'success')
} catch (e) {
showToast(e instanceof Error ? e.message : 'Failed', 'error')
} finally {
@ -237,7 +236,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
<div className="text-xl font-bold text-white font-mono">
{stats?.daily_drops?.toLocaleString() || total.toLocaleString()}
</div>
<div className="text-[10px] font-mono text-white/40 uppercase">Fresh drops (24h)</div>
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Fresh drops detected (24h)</div>
</div>
</div>
<button
@ -278,8 +277,8 @@ export function DropsTab({ showToast }: DropsTabProps) {
<button
onClick={() => setSelectedTld(null)}
className={clsx(
"px-2.5 py-1.5 text-[10px] font-mono uppercase border transition-colors",
selectedTld === null ? "border-accent bg-accent/10 text-accent" : "border-white/[0.08] text-white/40"
"px-3 py-1.5 text-[10px] font-mono uppercase border transition-colors",
selectedTld === null ? "border-accent bg-accent/10 text-accent font-bold" : "border-white/[0.08] text-white/40"
)}
>
All
@ -289,8 +288,8 @@ export function DropsTab({ showToast }: DropsTabProps) {
key={tld}
onClick={() => setSelectedTld(tld)}
className={clsx(
"px-2.5 py-1.5 text-[10px] font-mono uppercase border transition-colors flex items-center gap-1",
selectedTld === tld ? "border-accent bg-accent/10 text-accent" : "border-white/[0.08] text-white/40"
"px-3 py-1.5 text-[10px] font-mono uppercase border transition-colors flex items-center gap-1.5",
selectedTld === tld ? "border-accent bg-accent/10 text-accent font-bold" : "border-white/[0.08] text-white/40"
)}
>
<span className="text-xs">{flag}</span>.{tld}
@ -302,15 +301,15 @@ export function DropsTab({ showToast }: DropsTabProps) {
<button
onClick={() => setFiltersOpen(!filtersOpen)}
className={clsx(
"flex items-center justify-between w-full py-2 px-3 border transition-colors",
"flex items-center justify-between w-full py-2.5 px-4 border transition-colors",
filtersOpen ? "border-accent/30 bg-accent/[0.05]" : "border-white/[0.08] bg-white/[0.02]"
)}
>
<div className="flex items-center gap-2">
<Filter className="w-4 h-4 text-white/40" />
<span className="text-xs font-mono text-white/60">Filters</span>
<span className="text-xs font-mono text-white/60 uppercase tracking-widest">Advanced Filters</span>
{activeFiltersCount > 0 && (
<span className="px-1.5 py-0.5 text-[9px] font-bold bg-accent text-black">{activeFiltersCount}</span>
<span className="px-1.5 py-0.5 text-[9px] font-bold bg-accent text-black ml-1">{activeFiltersCount}</span>
)}
</div>
<ChevronRight className={clsx("w-4 h-4 text-white/30 transition-transform", filtersOpen && "rotate-90")} />
@ -318,27 +317,27 @@ export function DropsTab({ showToast }: DropsTabProps) {
{/* Filters Panel */}
{filtersOpen && (
<div className="p-3 border border-white/[0.08] bg-white/[0.02] space-y-3 animate-in fade-in slide-in-from-top-2 duration-200">
<div className="p-4 border border-white/[0.08] bg-white/[0.02] space-y-4 animate-in fade-in slide-in-from-top-2 duration-200">
{/* Length Filter */}
<div>
<div className="text-[9px] font-mono text-white/30 uppercase tracking-wider mb-2">Length</div>
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Domain Length</div>
<div className="flex gap-2 items-center">
<input
type="number"
value={minLength || ''}
onChange={(e) => setMinLength(e.target.value ? Number(e.target.value) : undefined)}
placeholder="Min"
className="w-16 bg-white/[0.02] border border-white/10 px-2 py-1.5 text-xs text-white placeholder:text-white/25 outline-none font-mono"
className="w-20 bg-white/[0.02] border border-white/10 px-3 py-2 text-xs text-white placeholder:text-white/20 outline-none font-mono focus:border-accent/30"
min={1}
max={63}
/>
<span className="text-white/20"></span>
<span className="text-white/10 px-1 font-mono text-xs">TO</span>
<input
type="number"
value={maxLength || ''}
onChange={(e) => setMaxLength(e.target.value ? Number(e.target.value) : undefined)}
placeholder="Max"
className="w-16 bg-white/[0.02] border border-white/10 px-2 py-1.5 text-xs text-white placeholder:text-white/25 outline-none font-mono"
className="w-20 bg-white/[0.02] border border-white/10 px-3 py-2 text-xs text-white placeholder:text-white/20 outline-none font-mono focus:border-accent/30"
min={1}
max={63}
/>
@ -350,15 +349,15 @@ export function DropsTab({ showToast }: DropsTabProps) {
<button
onClick={() => setExcludeNumeric(!excludeNumeric)}
className={clsx(
"flex-1 flex items-center justify-between py-2 px-3 border transition-colors",
excludeNumeric ? "border-white/20 bg-white/[0.05]" : "border-white/[0.08]"
"flex-1 flex items-center justify-between py-2.5 px-4 border transition-colors",
excludeNumeric ? "border-accent/30 bg-accent/5" : "border-white/[0.08]"
)}
>
<div className="flex items-center gap-2">
<Hash className="w-3.5 h-3.5 text-white/40" />
<span className="text-[10px] font-mono text-white/60">No numbers</span>
<div className="flex items-center gap-2.5">
<Hash className={clsx("w-3.5 h-3.5", excludeNumeric ? "text-accent" : "text-white/30")} />
<span className={clsx("text-[10px] font-mono uppercase tracking-wider", excludeNumeric ? "text-accent" : "text-white/50")}>Exclude Numeric</span>
</div>
<div className={clsx("w-3.5 h-3.5 border flex items-center justify-center", excludeNumeric ? "border-accent bg-accent" : "border-white/30")}>
<div className={clsx("w-3.5 h-3.5 border flex items-center justify-center", excludeNumeric ? "border-accent bg-accent" : "border-white/20")}>
{excludeNumeric && <span className="text-black text-[8px] font-bold"></span>}
</div>
</button>
@ -366,15 +365,15 @@ export function DropsTab({ showToast }: DropsTabProps) {
<button
onClick={() => setExcludeHyphen(!excludeHyphen)}
className={clsx(
"flex-1 flex items-center justify-between py-2 px-3 border transition-colors",
excludeHyphen ? "border-white/20 bg-white/[0.05]" : "border-white/[0.08]"
"flex-1 flex items-center justify-between py-2.5 px-4 border transition-colors",
excludeHyphen ? "border-accent/30 bg-accent/5" : "border-white/[0.08]"
)}
>
<div className="flex items-center gap-2">
<Ban className="w-3.5 h-3.5 text-white/40" />
<span className="text-[10px] font-mono text-white/60">No hyphens</span>
<div className="flex items-center gap-2.5">
<Ban className={clsx("w-3.5 h-3.5", excludeHyphen ? "text-accent" : "text-white/30")} />
<span className={clsx("text-[10px] font-mono uppercase tracking-wider", excludeHyphen ? "text-accent" : "text-white/50")}>Exclude Hyphen</span>
</div>
<div className={clsx("w-3.5 h-3.5 border flex items-center justify-center", excludeHyphen ? "border-accent bg-accent" : "border-white/30")}>
<div className={clsx("w-3.5 h-3.5 border flex items-center justify-center", excludeHyphen ? "border-accent bg-accent" : "border-white/20")}>
{excludeHyphen && <span className="text-black text-[8px] font-bold"></span>}
</div>
</button>
@ -382,57 +381,66 @@ export function DropsTab({ showToast }: DropsTabProps) {
</div>
)}
{/* Stats Bar */}
<div className="flex items-center justify-between text-[10px] font-mono text-white/40">
<span>{total.toLocaleString()} fresh drops</span>
{totalPages > 1 && <span>Page {page}/{totalPages}</span>}
{/* Results Header */}
<div className="flex items-center justify-between px-1 text-[10px] font-mono text-white/30 uppercase tracking-[0.1em]">
<div className="flex items-center gap-2">
<div className="w-1 h-1 bg-accent rounded-full" />
<span>{total.toLocaleString()} domains matching criteria</span>
</div>
{totalPages > 1 && <span>Page {page} of {totalPages}</span>}
</div>
{/* Results */}
{/* Results Table */}
{sortedItems.length === 0 ? (
<div className="text-center py-16 border border-dashed border-white/[0.08]">
<Globe className="w-8 h-8 text-white/10 mx-auto mb-3" />
<p className="text-white/40 text-sm font-mono">No fresh drops</p>
<p className="text-white/25 text-xs font-mono mt-1">Check back after the next sync</p>
<div className="text-center py-24 border border-dashed border-white/[0.08] bg-white/[0.01]">
<Globe className="w-12 h-12 text-white/5 mx-auto mb-4" />
<p className="text-white/40 text-sm font-mono uppercase tracking-widest font-bold">No fresh drops detected</p>
<p className="text-white/20 text-[10px] font-mono mt-3 uppercase tracking-wider max-w-xs mx-auto leading-relaxed">
The zone file comparison engine will update in the next 24h cycle
</p>
</div>
) : (
<>
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
<div className="border border-white/[0.08] bg-white/[0.01] overflow-hidden">
{/* Desktop Table Header */}
<div className="hidden lg:grid grid-cols-[1fr_60px_80px_100px] gap-4 px-3 py-2 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
<button onClick={() => handleSort('domain')} className="flex items-center gap-1 hover:text-white/60 text-left" title="Freshly dropped domain - was registered but not renewed">
Domain
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_140px] gap-4 px-5 py-3 text-[10px] font-mono text-white/30 uppercase tracking-[0.2em] border-b border-white/[0.08] bg-white/[0.02]">
<button onClick={() => handleSort('domain')} className="flex items-center gap-2 hover:text-white transition-colors text-left group">
<span className={clsx(sortField === 'domain' && "text-accent font-bold")}>Domain Name</span>
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
</button>
<button onClick={() => handleSort('length')} className="flex items-center gap-1 justify-center hover:text-white/60" title="Character count - shorter domains are more valuable">
Len
{sortField === 'length' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
<button onClick={() => handleSort('length')} className="flex items-center gap-2 justify-center hover:text-white transition-colors group">
<span className={clsx(sortField === 'length' && "text-accent font-bold")}>Length</span>
{sortField === 'length' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
</button>
<button onClick={() => handleSort('date')} className="flex items-center gap-1 justify-center hover:text-white/60" title="When the domain was detected as dropped">
When
{sortField === 'date' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
<button onClick={() => handleSort('date')} className="flex items-center gap-2 justify-center hover:text-white transition-colors group">
<span className={clsx(sortField === 'date' && "text-accent font-bold")}>Dropped</span>
{sortField === 'date' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
</button>
<div className="text-right">Actions</div>
<div className="text-right pr-2">Actions</div>
</div>
<div className="divide-y divide-white/[0.04]">
{sortedItems.map((item) => (
<div key={`${item.domain}.${item.tld}`} className="bg-[#020202] hover:bg-white/[0.02] transition-all">
<div key={`${item.domain}.${item.tld}`} className="bg-[#020202] hover:bg-white/[0.02] transition-all group">
{/* Mobile Row */}
<div className="lg:hidden p-3">
<div className="flex items-center justify-between gap-3 mb-2">
<div className="flex items-center gap-2 min-w-0 flex-1">
<button onClick={() => openAnalyze(`${item.domain}.${item.tld}`)} className="text-sm font-bold text-white font-mono truncate text-left">
{item.domain}<span className="text-white/40">.{item.tld}</span>
<div className="lg:hidden p-4">
<div className="flex items-center justify-between gap-3 mb-4">
<div className="flex flex-col min-w-0">
<button
onClick={() => openAnalyze(`${item.domain}.${item.tld}`)}
className="text-base font-bold text-white font-mono truncate text-left tracking-tight"
>
{item.domain}<span className="text-white/30">.{item.tld}</span>
</button>
</div>
<div className="flex items-center gap-2 shrink-0">
<div className="flex items-center gap-3 mt-1.5">
<span className={clsx(
"text-[10px] font-mono font-bold px-1.5 py-0.5",
item.length <= 5 ? "text-accent bg-accent/10" : "text-white/40 bg-white/5"
"text-[9px] font-mono font-bold px-2 py-0.5 border",
item.length <= 5 ? "text-accent border-accent/20 bg-accent/5" : "text-white/30 border-white/5 bg-white/5"
)}>
{item.length}
LEN: {item.length}
</span>
<span className="text-[10px] font-mono text-white/30">{formatTime(item.dropped_date)}</span>
<span className="text-[10px] font-mono text-white/20 uppercase tracking-wider">{formatTime(item.dropped_date)}</span>
</div>
</div>
</div>
@ -440,94 +448,104 @@ export function DropsTab({ showToast }: DropsTabProps) {
<button
onClick={() => track(`${item.domain}.${item.tld}`)}
disabled={tracking === `${item.domain}.${item.tld}`}
className="flex-1 py-2 text-[10px] font-bold uppercase tracking-wider border border-white/[0.08] text-white/40 flex items-center justify-center gap-1.5"
className="flex-1 h-10 text-[10px] font-bold uppercase tracking-widest border border-white/10 text-white/40 flex items-center justify-center gap-2 hover:bg-white/5 active:scale-95 transition-all"
>
{tracking === `${item.domain}.${item.tld}` ? <Loader2 className="w-3 h-3 animate-spin" /> : <Eye className="w-3 h-3" />}
{tracking === `${item.domain}.${item.tld}` ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-4 h-4" />}
Track
</button>
<button onClick={() => openAnalyze(`${item.domain}.${item.tld}`)} className="w-10 py-2 border border-white/[0.08] text-white/50 flex items-center justify-center">
<Shield className="w-3.5 h-3.5" />
<button
onClick={() => openAnalyze(`${item.domain}.${item.tld}`)}
className="w-12 h-10 border border-white/10 text-white/40 flex items-center justify-center hover:text-accent hover:border-accent/20 hover:bg-accent/5 transition-all"
>
<Shield className="w-4.5 h-4.5" />
</button>
<a
href={`https://www.namecheap.com/domains/registration/results/?domain=${item.domain}.${item.tld}`}
target="_blank"
rel="noopener noreferrer"
className="flex-1 py-2 bg-accent text-black text-[10px] font-bold uppercase flex items-center justify-center gap-1"
className="flex-1 h-10 bg-accent text-black text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-1.5 hover:bg-white active:scale-95 transition-all"
>
Get <ExternalLink className="w-3 h-3" />
Buy Now
</a>
</div>
</div>
{/* Desktop Row */}
<div className="hidden lg:grid grid-cols-[1fr_60px_80px_100px] gap-4 items-center p-4 group">
<div className="flex items-center gap-2 min-w-0 flex-1">
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_140px] gap-4 items-center px-5 py-3.5">
<div className="flex items-center gap-3 min-w-0">
<button
onClick={() => openAnalyze(`${item.domain}.${item.tld}`)}
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left"
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left tracking-tight"
>
{item.domain}<span className="text-white/40 group-hover:text-accent/60">.{item.tld}</span>
{item.domain}<span className="text-white/30 group-hover:text-accent/40">.{item.tld}</span>
</button>
</div>
<div className="text-center">
<span className={clsx(
"text-[10px] font-mono font-bold px-1.5 py-0.5",
item.length <= 5 ? "text-accent bg-accent/10" : item.length <= 8 ? "text-amber-400 bg-amber-400/10" : "text-white/40 bg-white/5"
"text-[10px] font-mono font-bold px-2 py-0.5 border",
item.length <= 5 ? "text-accent border-accent/20 bg-accent/5" : item.length <= 8 ? "text-amber-400 border-amber-400/20 bg-amber-400/5" : "text-white/30 border-white/5 bg-white/5"
)}>
{item.length}
</span>
</div>
<div className="text-center">
<span className="text-[10px] font-mono text-white/50">{formatTime(item.dropped_date)}</span>
<span className="text-[10px] font-mono text-white/40 uppercase tracking-wider">{formatTime(item.dropped_date)}</span>
</div>
<div className="flex items-center justify-end gap-1.5 opacity-50 group-hover:opacity-100 transition-opacity">
<div className="flex items-center justify-end gap-1.5 opacity-0 group-hover:opacity-100 transition-all duration-300 transform translate-x-2 group-hover:translate-x-0">
<button
onClick={() => track(`${item.domain}.${item.tld}`)}
disabled={tracking === `${item.domain}.${item.tld}`}
className="w-6 h-6 flex items-center justify-center border border-white/10 text-white/30 hover:text-white hover:bg-white/5"
className="w-8.5 h-8.5 flex items-center justify-center border border-white/10 text-white/30 hover:text-white hover:bg-white/5 transition-all"
title="Add to Watchlist"
>
{tracking === `${item.domain}.${item.tld}` ? <Loader2 className="w-3 h-3 animate-spin" /> : <Eye className="w-3 h-3" />}
{tracking === `${item.domain}.${item.tld}` ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-4 h-4" />}
</button>
<button
onClick={() => openAnalyze(`${item.domain}.${item.tld}`)}
className="w-6 h-6 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/20 hover:bg-accent/10"
className="w-8.5 h-8.5 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
title="Deep Analysis"
>
<Shield className="w-3 h-3" />
<Shield className="w-4 h-4" />
</button>
<a
href={`https://www.namecheap.com/domains/registration/results/?domain=${item.domain}.${item.tld}`}
target="_blank"
rel="noopener noreferrer"
className="h-6 px-2 bg-accent text-black text-[10px] font-bold flex items-center gap-1 hover:bg-white"
className="h-8.5 px-4 bg-accent text-black text-[10px] font-black uppercase tracking-widest flex items-center gap-1.5 hover:bg-white transition-all shadow-[0_0_15px_-5px_rgba(34,211,126,0.4)]"
>
Get
Buy
</a>
</div>
</div>
</div>
))}
</div>
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="flex items-center justify-center gap-1 pt-2">
<div className="flex items-center justify-center gap-1 pt-6">
<button
onClick={() => handlePageChange(page - 1)}
disabled={page === 1}
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed"
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-20 disabled:cursor-not-allowed transition-all"
>
<ChevronLeft className="w-4 h-4" />
<ChevronLeft className="w-5 h-5" />
</button>
<span className="text-xs text-white/50 font-mono px-3">{page}/{totalPages}</span>
<div className="flex items-center bg-white/[0.02] border border-white/[0.08] px-5 h-10">
<span className="text-[11px] text-white/40 font-mono uppercase tracking-widest">
Page <span className="text-white font-bold">{page}</span> / {totalPages}
</span>
</div>
<button
onClick={() => handlePageChange(page + 1)}
disabled={page === totalPages}
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed"
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-20 disabled:cursor-not-allowed transition-all"
>
<ChevronRight className="w-4 h-4" />
<ChevronRight className="w-5 h-5" />
</button>
</div>
)}

View File

@ -298,7 +298,7 @@ export function SearchTab({ showToast }: SearchTabProps) {
onClick={() => handleSearch(searchQuery)}
disabled={!searchQuery.trim()}
className={clsx(
"h-full px-4 py-4 text-sm font-bold uppercase tracking-wider transition-all",
"h-full px-6 py-4 text-xs font-bold uppercase tracking-widest transition-all shrink-0 border-l border-white/10",
searchQuery.trim() ? "bg-accent text-black hover:bg-white" : "bg-white/5 text-white/20"
)}
>
@ -308,115 +308,122 @@ export function SearchTab({ showToast }: SearchTabProps) {
</div>
{/* Stats Bar */}
<div className="flex items-center justify-between text-[10px] font-mono text-white/40">
<span>Enter a name (checks 10 TLDs) or full domain (e.g. example.com)</span>
<span className="flex items-center gap-1">
<Sparkles className="w-3 h-3" />
RDAP/WHOIS
</span>
<div className="flex items-center justify-between px-1 text-[10px] font-mono text-white/30 uppercase tracking-[0.1em]">
<div className="flex items-center gap-2">
<div className="w-1 h-1 bg-accent rounded-full animate-pulse" />
<span>Enter a name or full domain</span>
</div>
<div className="flex items-center gap-2">
<Sparkles className="w-3 h-3 text-accent/60" />
<span>RDAP/WHOIS READY</span>
</div>
</div>
{/* Single Domain Result */}
{searchMode === 'single' && searchResult && (
<div className="animate-in fade-in slide-in-from-bottom-2 duration-200">
<div className="animate-in fade-in slide-in-from-bottom-2 duration-300">
{searchResult.loading ? (
<div className="flex items-center justify-center gap-3 py-12 border border-white/[0.08] bg-white/[0.02]">
<Loader2 className="w-6 h-6 animate-spin text-accent" />
<span className="text-sm text-white/50 font-mono">Checking availability...</span>
<div className="flex flex-col items-center justify-center py-20 border border-white/[0.08] bg-white/[0.01]">
<div className="relative">
<div className="absolute inset-0 bg-accent/20 blur-xl rounded-full animate-pulse" />
<Loader2 className="w-8 h-8 animate-spin text-accent relative z-10" />
</div>
<span className="text-[10px] text-white/40 font-mono mt-4 uppercase tracking-[0.2em]">Checking global availability...</span>
</div>
) : searchResult.error ? (
// Error state (invalid TLD, check failed, etc.)
<div className="border-2 border-rose-500/30 overflow-hidden bg-[#020202]">
<div className="p-4">
// Error state
<div className="border border-rose-500/30 bg-rose-500/[0.02]">
<div className="p-4 lg:p-6">
<div className="flex items-center gap-4">
<div className="w-12 h-12 flex items-center justify-center border border-rose-500/30 bg-rose-500/10 shrink-0">
<div className="w-14 h-14 flex items-center justify-center border border-rose-500/30 bg-rose-500/10 shrink-0">
<XCircle className="w-6 h-6 text-rose-500" />
</div>
<div className="min-w-0 flex-1">
<div className="text-lg font-bold text-white font-mono truncate">{searchResult.domain}</div>
<div className="text-sm text-rose-400 mt-1">{searchResult.error}</div>
<div className="text-xl font-bold text-white font-mono truncate tracking-tight">{searchResult.domain}</div>
<div className="text-[11px] font-mono text-rose-400 mt-1 uppercase tracking-wider">{searchResult.error}</div>
</div>
</div>
</div>
</div>
) : (
<div className={clsx(
"border-2 overflow-hidden bg-[#020202]",
searchResult.is_available ? "border-accent/40" : "border-rose-500/30"
"border bg-[#020202] transition-all duration-500",
searchResult.is_available ? "border-accent/30 shadow-[0_0_40px_-20px_rgba(34,211,126,0.2)]" : "border-rose-500/20"
)}>
{/* Result Row */}
<div className="p-4">
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-4 min-w-0 flex-1">
{/* Status Icon */}
<div className="p-4 lg:p-6">
<div className="flex flex-col lg:grid lg:grid-cols-[1fr_auto] gap-6 items-center">
<div className="flex items-center gap-5 min-w-0 w-full">
{/* Status Indicator */}
<div className={clsx(
"w-12 h-12 flex items-center justify-center border shrink-0",
searchResult.is_available ? "bg-accent/10 border-accent/30" : "bg-rose-500/10 border-rose-500/30"
"w-16 h-16 flex items-center justify-center border shrink-0 transition-colors duration-500",
searchResult.is_available ? "bg-accent/10 border-accent/20" : "bg-rose-500/5 border-rose-500/20"
)}>
{searchResult.is_available ? (
<CheckCircle2 className="w-6 h-6 text-accent" />
<CheckCircle2 className="w-8 h-8 text-accent" />
) : (
<XCircle className="w-6 h-6 text-rose-500" />
<XCircle className="w-8 h-8 text-rose-500" />
)}
</div>
{/* Domain Info */}
<div className="min-w-0 flex-1">
<div className={clsx(
"text-lg font-bold font-mono truncate",
"text-2xl lg:text-3xl font-bold font-mono truncate tracking-tight",
searchResult.is_available ? "text-white" : "text-rose-400"
)}>{searchResult.domain}</div>
<div className="flex items-center gap-3 text-[10px] font-mono text-white/40 mt-1">
)}>
{searchResult.domain}
</div>
<div className="flex flex-wrap items-center gap-y-2 gap-x-4 mt-2">
<span className={clsx(
"px-2 py-0.5 uppercase font-bold",
searchResult.is_available ? "bg-accent/20 text-accent" : "bg-rose-500/20 text-rose-400"
"px-2 py-0.5 text-[9px] font-bold uppercase tracking-widest border",
searchResult.is_available
? "bg-accent/10 border-accent/20 text-accent"
: "bg-rose-500/10 border-rose-500/20 text-rose-400"
)}>
{searchResult.is_available ? 'Available' : 'Taken'}
</span>
{searchResult.registrar && (
<>
<span className="text-white/10">|</span>
<span className="flex items-center gap-1" title="Current registrar holding this domain">
<div className="flex items-center gap-1.5 text-[10px] font-mono text-white/30 uppercase tracking-wider">
<Building className="w-3 h-3" />
{searchResult.registrar}
</span>
</>
</div>
)}
{searchResult.expiration_date && (
<>
<span className="text-white/10">|</span>
<span className="flex items-center gap-1" title="Domain expiration date - monitor for potential drop">
<Calendar className="w-3 h-3" />
Expires {new Date(searchResult.expiration_date).toLocaleDateString()}
</span>
</>
<div className="flex items-center gap-1.5 text-[10px] font-mono text-white/30 uppercase tracking-wider">
<Clock className="w-3 h-3" />
Expires {new Date(searchResult.expiration_date).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })}
</div>
)}
</div>
</div>
</div>
{/* Actions */}
<div className="flex items-center gap-2 shrink-0">
<div className="flex items-center gap-2 w-full lg:w-auto">
<button
onClick={() => openAnalyze(searchResult.domain)}
className="w-9 h-9 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/20 hover:bg-accent/10 transition-colors"
title="Deep Analysis - View SEO metrics, backlinks, and valuation"
className="flex-1 lg:w-12 lg:h-12 h-12 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
title="Deep Analysis"
>
<Shield className="w-4 h-4" />
<Shield className="w-5 h-5" />
</button>
<button
onClick={handleAddToWatchlist}
disabled={addingToWatchlist}
className={clsx(
"w-9 h-9 flex items-center justify-center border transition-colors",
"flex-1 lg:w-12 lg:h-12 h-12 flex items-center justify-center border transition-all",
searchResult.is_available
? "border-white/10 text-white/30 hover:text-white hover:bg-white/5"
: "border-rose-500/30 text-rose-400 hover:bg-rose-500/10"
: "border-rose-500/20 text-rose-400/40 hover:text-rose-400 hover:bg-rose-500/5"
)}
title={searchResult.is_available ? "Add to watchlist to track this domain" : "Monitor this domain and get notified when it drops"}
title={searchResult.is_available ? "Track domain" : "Monitor drop"}
>
{addingToWatchlist ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
{addingToWatchlist ? <Loader2 className="w-5 h-5 animate-spin" /> : <Eye className="w-5 h-5" />}
</button>
{searchResult.is_available ? (
@ -424,20 +431,18 @@ export function SearchTab({ showToast }: SearchTabProps) {
href={`https://www.namecheap.com/domains/registration/results/?domain=${searchResult.domain}`}
target="_blank"
rel="noopener noreferrer"
className="h-9 px-4 bg-accent text-black text-xs font-bold uppercase tracking-wider flex items-center gap-2 hover:bg-white transition-colors"
title="Register this domain now via Namecheap"
className="flex-[2] lg:flex-none h-12 px-8 bg-accent text-black text-[11px] font-black uppercase tracking-[0.1em] flex items-center justify-center gap-2 hover:bg-white transition-all shadow-[0_0_20px_-10px_rgba(34,211,126,0.5)]"
>
Register
<ArrowRight className="w-3.5 h-3.5" />
Buy Now
<ArrowRight className="w-4 h-4" />
</a>
) : (
<button
onClick={handleAddToWatchlist}
disabled={addingToWatchlist}
className="h-9 px-4 bg-rose-500/20 text-rose-400 text-xs font-bold uppercase tracking-wider flex items-center gap-2 hover:bg-rose-500/30 border border-rose-500/30 transition-colors"
title="Add to watchlist and get notified when this domain becomes available"
className="flex-[2] lg:flex-none h-12 px-8 bg-rose-500/10 text-rose-400 text-[11px] font-bold uppercase tracking-[0.1em] flex items-center justify-center gap-2 hover:bg-rose-500/20 border border-rose-500/20 transition-all"
>
{addingToWatchlist ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-3.5 h-3.5" />}
{addingToWatchlist ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
Monitor
</button>
)}
@ -451,39 +456,44 @@ export function SearchTab({ showToast }: SearchTabProps) {
{/* Multi-TLD Results */}
{searchMode === 'multi' && tldResults.length > 0 && (
<div className="animate-in fade-in slide-in-from-bottom-2 duration-200">
<div className="border border-white/[0.08] bg-[#020202] overflow-hidden">
<div className="animate-in fade-in slide-in-from-bottom-2 duration-400">
<div className="border border-white/[0.08] bg-[#020202]">
{/* Header */}
<div className="px-4 py-3 border-b border-white/[0.08] flex items-center justify-between">
<div className="flex items-center gap-2">
<Globe className="w-4 h-4 text-accent" />
<span className="text-[10px] font-mono text-white/40 uppercase tracking-wider">
Checking {POPULAR_TLDS.length} extensions for "{searchQuery.toLowerCase().replace(/\s+/g, '')}"
<div className="px-5 py-4 border-b border-white/[0.08] flex items-center justify-between bg-white/[0.01]">
<div className="flex items-center gap-3">
<Globe className="w-4 h-4 text-accent/60" />
<span className="text-[10px] font-mono text-white/50 uppercase tracking-[0.2em]">
Global availability check: <span className="text-white">"{searchQuery}"</span>
</span>
</div>
<span className="text-[10px] font-mono text-accent">
{tldResults.filter(r => r.is_available === true).length} available
<div className="flex items-center gap-4">
<div className="flex items-center gap-1.5">
<div className="w-1.5 h-1.5 rounded-full bg-accent" />
<span className="text-[10px] font-mono text-accent uppercase font-bold">
{tldResults.filter(r => r.is_available === true).length} Free
</span>
</div>
<div className="flex items-center gap-1.5">
<div className="w-1.5 h-1.5 rounded-full bg-rose-500/40" />
<span className="text-[10px] font-mono text-white/20 uppercase">
{tldResults.filter(r => r.is_available === false).length} Taken
</span>
</div>
</div>
</div>
{/* TLD Grid */}
<div className="p-3 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2">
<div className="p-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
{tldResults.map((result) => (
<div
key={result.tld}
title={result.loading
? `Checking ${result.domain}...`
: result.is_available
? `${result.domain} is available! Click to see details.`
: `${result.domain} is already registered`
}
className={clsx(
"p-3 border transition-all",
"relative p-4 border transition-all duration-300 group",
result.loading
? "border-white/[0.08] bg-white/[0.02]"
? "border-white/[0.05] bg-white/[0.01]"
: result.is_available
? "border-accent/40 bg-accent/[0.05] hover:bg-accent/10 cursor-pointer"
: "border-rose-500/20 bg-rose-500/[0.02]"
? "border-accent/20 bg-accent/[0.02] hover:border-accent/60 hover:bg-accent/[0.08] cursor-pointer"
: "border-white/[0.05] bg-white/[0.01] opacity-60"
)}
onClick={() => {
if (result.is_available && !result.loading) {
@ -495,30 +505,38 @@ export function SearchTab({ showToast }: SearchTabProps) {
}}
>
{result.loading ? (
<div className="flex items-center justify-center py-1">
<Loader2 className="w-4 h-4 animate-spin text-white/30" />
<div className="flex items-center justify-center py-2">
<Loader2 className="w-5 h-5 animate-spin text-white/10" />
</div>
) : (
<>
<div className="flex items-center justify-between mb-1">
<div className="flex items-center justify-between mb-2">
<span className={clsx(
"text-xs font-mono font-bold",
result.is_available ? "text-accent" : "text-rose-400/60"
"text-sm font-mono font-black tracking-tight",
result.is_available ? "text-white group-hover:text-accent" : "text-white/20"
)}>
.{result.tld}
</span>
{result.is_available ? (
<CheckCircle2 className="w-3.5 h-3.5 text-accent" />
<div className="w-5 h-5 rounded-full bg-accent/10 border border-accent/20 flex items-center justify-center">
<CheckCircle2 className="w-3 h-3 text-accent" />
</div>
) : (
<XCircle className="w-3.5 h-3.5 text-rose-400/40" />
<XCircle className="w-4 h-4 text-white/10" />
)}
</div>
<div className={clsx(
"text-[9px] font-mono uppercase",
result.is_available ? "text-accent/60" : "text-rose-400/40"
"text-[9px] font-mono uppercase tracking-[0.1em] font-bold",
result.is_available ? "text-accent" : "text-white/20"
)}>
{result.is_available ? 'Available' : 'Taken'}
</div>
{result.is_available && (
<div className="absolute bottom-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity">
<ArrowRight className="w-3 h-3 text-accent" />
</div>
)}
</>
)}
</div>
@ -526,8 +544,8 @@ export function SearchTab({ showToast }: SearchTabProps) {
</div>
{/* Footer hint */}
<div className="px-4 py-2 border-t border-white/[0.06] text-[9px] font-mono text-white/30 text-center">
Click an available extension to see details Add ".tld" to your search for specific extension
<div className="px-4 py-3 border-t border-white/[0.04] bg-white/[0.01] text-[9px] font-mono text-white/20 text-center uppercase tracking-widest">
Click an available extension to analyze and buy
</div>
</div>
</div>
@ -535,23 +553,23 @@ export function SearchTab({ showToast }: SearchTabProps) {
{/* Recent Searches */}
{!searchResult && recentSearches.length > 0 && (
<div className="border border-white/[0.08] bg-white/[0.02]">
<div className="px-4 py-3 border-b border-white/[0.08] flex items-center justify-between">
<div className="border border-white/[0.08] bg-white/[0.01]">
<div className="px-5 py-3 border-b border-white/[0.08] flex items-center justify-between bg-white/[0.01]">
<div className="flex items-center gap-2">
<History className="w-4 h-4 text-white/30" />
<span className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Recent Searches</span>
<History className="w-4 h-4 text-white/20" />
<span className="text-[10px] font-mono text-white/40 uppercase tracking-[0.2em]">Recent History</span>
</div>
<button
onClick={() => {
setRecentSearches([])
localStorage.removeItem('pounce_recent_searches')
}}
className="text-[10px] font-mono text-white/30 hover:text-white transition-colors"
className="text-[10px] font-mono text-white/20 hover:text-white transition-colors uppercase tracking-widest"
>
Clear
Clear All
</button>
</div>
<div className="p-3">
<div className="p-4">
<div className="flex flex-wrap gap-2">
{recentSearches.map((domain) => (
<button
@ -560,9 +578,9 @@ export function SearchTab({ showToast }: SearchTabProps) {
setSearchQuery(domain)
handleSearch(domain)
}}
className="group px-3 py-2 border border-white/[0.08] bg-[#020202] hover:border-accent/30 hover:bg-accent/[0.03] transition-all"
className="group px-4 py-2 border border-white/[0.08] bg-white/[0.01] hover:border-accent/30 hover:bg-accent/[0.03] transition-all"
>
<span className="text-xs font-mono text-white/60 group-hover:text-accent transition-colors">{domain}</span>
<span className="text-xs font-mono text-white/40 group-hover:text-accent transition-colors">{domain}</span>
</button>
))}
</div>

View File

@ -159,60 +159,60 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
}
}
const currentGeo = GEOS.find(g => g.code === geo)
const availableResults = results.filter(r => r.available)
const takenResults = results.filter(r => !r.available)
return (
<div className="space-y-6">
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* HEADER */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 pb-4 border-b border-white/10">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-5 border-b border-white/[0.08]">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-orange-500/10 border border-orange-500/20 flex items-center justify-center shrink-0">
<Flame className="w-6 h-6 text-orange-400" />
</div>
<div>
<h2 className="text-xl font-bold text-white flex items-center gap-2">
<Flame className="w-5 h-5 text-orange-400" />
Trend Surfer
</h2>
<p className="text-sm text-white/60 mt-1">
Find domains for trending topics {currentGeo?.flag} {currentGeo?.name}
</p>
<h2 className="text-xl font-bold text-white font-mono tracking-tight">Trend Surfer</h2>
<p className="text-[10px] font-mono text-white/40 uppercase tracking-widest mt-1">Ride the viral wave with AI-powered domain hunt</p>
</div>
</div>
<div className="flex items-center gap-2">
<div className="relative">
<select
value={geo}
onChange={(e) => { setGeo(e.target.value); setSelected(null); setKeywords([]); setResults([]) }}
className="h-10 px-3 bg-white/5 border border-white/10 text-sm text-white outline-none"
className="h-10 pl-4 pr-10 bg-white/[0.02] border border-white/10 text-xs font-mono text-white uppercase tracking-widest appearance-none outline-none focus:border-accent/30 transition-all cursor-pointer"
>
{GEOS.map(g => (
<option key={g.code} value={g.code}>{g.flag} {g.name}</option>
))}
</select>
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-white/30 pointer-events-none" />
</div>
<button
onClick={loadTrends}
disabled={loading}
className="h-10 w-10 flex items-center justify-center border border-white/10 text-white/60 hover:text-white hover:bg-white/5"
className="h-10 w-10 flex items-center justify-center border border-white/10 text-white/40 hover:text-white hover:bg-white/5 transition-all"
>
<RefreshCw className={clsx("w-4 h-4", loading && "animate-spin")} />
</button>
</div>
</div>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* TRENDS GRID */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<div>
<p className="text-xs text-white/50 font-mono uppercase tracking-wider mb-3">
Select a trending topic
</p>
{/* Trends Grid */}
<div className="space-y-3">
<div className="flex items-center gap-2 px-1 text-[10px] font-mono text-white/30 uppercase tracking-[0.2em]">
<div className="w-1 h-1 bg-orange-500 rounded-full animate-pulse" />
<span>Real-time Viral Topics ({geo})</span>
</div>
{loading ? (
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
{[...Array(8)].map((_, i) => (
<div key={i} className="h-14 bg-white/5 animate-pulse" />
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2">
{[...Array(12)].map((_, i) => (
<div key={i} className="h-14 bg-white/[0.01] border border-white/[0.05] animate-pulse" />
))}
</div>
) : (
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2">
{trends.slice(0, 12).map((t, idx) => {
const isSelected = selected === t.title
return (
@ -220,19 +220,22 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
key={t.title}
onClick={() => selectTrend(t.title)}
className={clsx(
"relative p-3 text-left border transition-all",
"relative px-4 py-3.5 text-left border transition-all duration-300 group overflow-hidden",
isSelected
? "border-accent bg-accent/10"
: "border-white/10 bg-white/[0.02] hover:border-white/20 hover:bg-white/[0.04]"
? "border-accent bg-accent/10 shadow-[0_0_20px_-10px_rgba(34,211,126,0.3)]"
: "border-white/[0.08] bg-white/[0.01] hover:border-white/20 hover:bg-white/[0.03]"
)}
>
{idx < 3 && <span className="absolute top-1.5 right-1.5 text-[10px]">🔥</span>}
<p className={clsx(
"text-sm font-medium truncate pr-4",
isSelected ? "text-accent" : "text-white"
<div className="relative z-10 flex items-center justify-between gap-2">
<span className={clsx(
"text-xs font-bold font-mono truncate tracking-tight",
isSelected ? "text-accent" : "text-white/70 group-hover:text-white"
)}>
{t.title}
</p>
</span>
{idx < 3 && !isSelected && <Flame className="w-3 h-3 text-orange-500/40 group-hover:text-orange-500 transition-colors" />}
</div>
{isSelected && <div className="absolute top-0 right-0 w-1.5 h-1.5 bg-accent" />}
</button>
)
})}
@ -240,84 +243,73 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
)}
</div>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* KEYWORD BUILDER */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* Keyword Builder */}
{selected && (
<div className="bg-white/[0.02] border border-white/10 p-4 sm:p-5 space-y-5">
{/* Selected Trend Header */}
<div className="flex items-center justify-between">
<div className="border border-white/[0.08] bg-white/[0.01] overflow-hidden animate-in fade-in slide-in-from-top-4 duration-500">
<div className="px-5 py-4 border-b border-white/[0.08] flex items-center justify-between bg-white/[0.01]">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-accent/10 border border-accent/20 flex items-center justify-center">
<Sparkles className="w-4 h-4 text-accent" />
</div>
<div>
<p className="text-xs text-white/50 font-mono uppercase tracking-wider">Building domains for</p>
<h3 className="text-lg font-bold text-white">{selected}</h3>
<div className="text-[10px] font-mono text-white/30 uppercase tracking-[0.2em]">Target Concept</div>
<div className="text-sm font-bold text-white uppercase tracking-wider">{selected}</div>
</div>
</div>
<button
onClick={() => { setSelected(null); setKeywords([]); setResults([]) }}
className="p-2 text-white/40 hover:text-white"
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/30 hover:text-white hover:bg-white/5 transition-all"
>
<X className="w-4 h-4" />
</button>
</div>
<div className="p-5 space-y-6">
{/* Keywords */}
<div>
<div className="flex items-center gap-2 mb-3">
<p className="text-xs text-white/60 font-mono uppercase tracking-wider">Keywords to check</p>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-[10px] font-mono text-white/30 uppercase tracking-widest">Constructed Keywords</span>
{aiLoading && (
<span className="flex items-center gap-1 text-[10px] text-purple-400">
<span className="flex items-center gap-2 text-[9px] font-mono text-purple-400 uppercase font-bold animate-pulse">
<Loader2 className="w-3 h-3 animate-spin" />
AI expanding...
AI Expansion in progress...
</span>
)}
{hasAI && keywords.length > 1 && !aiLoading && (
<span className="flex items-center gap-1 text-[10px] text-purple-400">
<Sparkles className="w-3 h-3" />
AI expanded
</span>
)}
{!hasAI && (
<Link href="/pricing" className="flex items-center gap-1 text-[10px] text-white/40 hover:text-accent">
<Lock className="w-3 h-3" />
Upgrade for AI
</Link>
)}
</div>
<div className="flex flex-wrap gap-2 mb-3">
<div className="flex flex-wrap gap-2">
{keywords.map((kw, idx) => (
<span
key={kw}
className={clsx(
"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-mono border",
"inline-flex items-center gap-2 px-3 py-1.5 text-xs font-mono border transition-all",
idx === 0
? "bg-accent/10 border-accent/30 text-accent"
: "bg-purple-500/10 border-purple-500/30 text-purple-300"
? "bg-accent/10 border-accent/30 text-accent font-bold"
: "bg-purple-500/10 border-purple-500/20 text-purple-300"
)}
>
{kw}
{keywords.length > 1 && (
<button onClick={() => removeKeyword(kw)} className="hover:text-white">
<button onClick={() => removeKeyword(kw)} className="text-white/20 hover:text-rose-400 transition-colors">
<X className="w-3 h-3" />
</button>
)}
</span>
))}
{/* Add custom keyword */}
<div className="flex">
<div className="relative">
<input
value={customKw}
onChange={(e) => setCustomKw(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addKeyword()}
placeholder="+ add"
className="w-20 px-2 py-1.5 bg-transparent border border-white/10 text-sm font-mono text-white placeholder:text-white/30 outline-none focus:border-white/30"
placeholder="+ CUSTOM KW"
className="w-28 px-3 py-1.5 bg-white/[0.03] border border-white/10 text-[10px] font-mono text-white placeholder:text-white/20 outline-none focus:border-accent/30 uppercase tracking-wider transition-all"
/>
</div>
</div>
</div>
{/* TLDs */}
<div>
<p className="text-xs text-white/60 font-mono uppercase tracking-wider mb-3">TLDs</p>
<div className="flex flex-wrap gap-2">
<div className="space-y-3">
<span className="text-[10px] font-mono text-white/30 uppercase tracking-widest">Selected Extensions</span>
<div className="flex flex-wrap gap-1.5">
{TLDS.map(tld => (
<button
key={tld}
@ -325,10 +317,10 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
prev.includes(tld) ? prev.filter(t => t !== tld) : [...prev, tld]
)}
className={clsx(
"px-3 py-2 text-sm font-mono border transition-all",
"px-3 py-1.5 text-[10px] font-mono border transition-all uppercase tracking-widest",
tlds.includes(tld)
? "border-accent bg-accent/10 text-accent"
: "border-white/10 text-white/50 hover:text-white"
? "border-accent bg-accent/10 text-accent font-black"
: "border-white/10 text-white/40 hover:text-white hover:bg-white/5"
)}
>
.{tld}
@ -342,56 +334,62 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
onClick={checkAvailability}
disabled={checking || tlds.length === 0 || keywords.length === 0}
className={clsx(
"w-full py-4 text-sm font-bold uppercase tracking-wider flex items-center justify-center gap-2 transition-all",
"w-full py-4 text-[11px] font-black uppercase tracking-[0.2em] flex items-center justify-center gap-3 transition-all",
checking || tlds.length === 0 || keywords.length === 0
? "bg-white/10 text-white/40"
: "bg-accent text-black hover:bg-white"
? "bg-white/5 text-white/20 border border-white/5"
: "bg-accent text-black hover:bg-white shadow-[0_0_30px_-10px_rgba(34,211,126,0.4)]"
)}
>
{checking ? <Loader2 className="w-4 h-4 animate-spin" /> : <Zap className="w-4 h-4" />}
Check {keywords.length} × {tlds.length} = {keywords.length * tlds.length} Domains
Check {keywords.length * tlds.length} Variations
</button>
</div>
</div>
)}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* RESULTS */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* Results */}
{results.length > 0 && (
<div className="space-y-4">
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
{/* Available */}
{availableResults.length > 0 && (
<div>
<p className="text-sm text-accent font-mono uppercase tracking-wider mb-3 flex items-center gap-2">
<span className="w-2 h-2 bg-accent rounded-full" />
{availableResults.length} Available
<div className="space-y-3">
<div className="flex items-center justify-between px-1">
<p className="text-[10px] font-mono text-accent uppercase tracking-[0.2em] font-black flex items-center gap-2">
<span className="w-1.5 h-1.5 bg-accent rounded-full animate-pulse" />
{availableResults.length} High-Potential Assets Identified
</p>
<div className="space-y-1.5">
<div className="flex gap-4">
<button onClick={() => setResults(availableResults.map(r => ({ domain: r.domain, available: true })))} className="text-[9px] font-mono text-white/20 hover:text-accent uppercase tracking-widest transition-colors">
Copy List
</button>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{availableResults.map(r => (
<div key={r.domain} className="flex items-center justify-between p-3 bg-accent/5 border border-accent/20 hover:bg-accent/10 transition-colors">
<div key={r.domain} className="flex items-center justify-between p-4 bg-accent/[0.02] border border-accent/20 hover:border-accent/40 hover:bg-accent/[0.04] transition-all group">
<button
onClick={() => openAnalyze(r.domain)}
className="text-sm font-mono text-white hover:text-accent truncate"
className="text-sm font-bold font-mono text-white group-hover:text-accent truncate tracking-tight transition-colors"
>
{r.domain}
</button>
<div className="flex items-center gap-1 shrink-0">
<button onClick={() => copy(r.domain)} className="w-8 h-8 flex items-center justify-center text-white/50 hover:text-white">
<div className="flex items-center gap-1.5 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity">
<button onClick={() => copy(r.domain)} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-white hover:bg-white/5" title="Copy">
{copied === r.domain ? <Check className="w-3.5 h-3.5 text-accent" /> : <Copy className="w-3.5 h-3.5" />}
</button>
<button onClick={() => track(r.domain)} disabled={tracking === r.domain} className="w-8 h-8 flex items-center justify-center text-white/50 hover:text-white">
<button onClick={() => track(r.domain)} disabled={tracking === r.domain} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-white hover:bg-white/5" title="Track">
{tracking === r.domain ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-3.5 h-3.5" />}
</button>
<button onClick={() => openAnalyze(r.domain)} className="w-8 h-8 flex items-center justify-center text-white/50 hover:text-accent">
<button onClick={() => openAnalyze(r.domain)} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-accent hover:border-accent/20 hover:bg-accent/5" title="Analyze">
<Shield className="w-3.5 h-3.5" />
</button>
<a
href={`https://www.namecheap.com/domains/registration/results/?domain=${r.domain}`}
target="_blank"
rel="noopener noreferrer"
className="h-8 px-3 bg-accent text-black text-[10px] font-bold flex items-center gap-1 hover:bg-white"
className="h-8 px-3 bg-accent text-black text-[10px] font-black uppercase tracking-widest flex items-center gap-1.5 hover:bg-white"
>
<ShoppingCart className="w-3 h-3" />
Buy
</a>
</div>
@ -404,16 +402,16 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
{/* Taken (collapsed) */}
{takenResults.length > 0 && (
<details className="group">
<summary className="text-sm text-white/40 font-mono uppercase tracking-wider cursor-pointer flex items-center gap-2 py-2 hover:text-white/60">
<summary className="text-[10px] font-mono text-white/20 uppercase tracking-[0.2em] cursor-pointer flex items-center gap-2 py-3 hover:text-white/40 transition-colors list-none border-t border-white/[0.04]">
<ChevronDown className="w-3 h-3 group-open:rotate-180 transition-transform" />
{takenResults.length} Taken
{takenResults.length} Registered Variations
</summary>
<div className="space-y-1.5 mt-2">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2 mt-3 animate-in slide-in-from-top-2">
{takenResults.map(r => (
<div key={r.domain} className="flex items-center justify-between p-2.5 bg-white/[0.02] border border-white/5">
<span className="text-sm font-mono text-white/40 truncate">{r.domain}</span>
<button onClick={() => openAnalyze(r.domain)} className="text-white/30 hover:text-white">
<Shield className="w-3.5 h-3.5" />
<div key={r.domain} className="flex items-center justify-between px-3 py-2 bg-white/[0.01] border border-white/[0.05] group">
<span className="text-[11px] font-mono text-white/20 truncate group-hover:text-white/40 transition-colors">{r.domain}</span>
<button onClick={() => openAnalyze(r.domain)} className="text-white/10 hover:text-accent transition-colors">
<Shield className="w-3 h-3" />
</button>
</div>
))}
@ -425,10 +423,12 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
{/* Empty State */}
{!selected && !loading && trends.length > 0 && (
<div className="text-center py-12 border border-dashed border-white/10">
<Globe className="w-10 h-10 text-white/20 mx-auto mb-3" />
<p className="text-white/50">Select a trending topic above</p>
<p className="text-sm text-white/30 mt-1">We'll find available domains for you</p>
<div className="text-center py-24 border border-dashed border-white/[0.08] bg-white/[0.01]">
<Globe className="w-12 h-12 text-white/5 mx-auto mb-4" />
<p className="text-white/40 text-sm font-mono uppercase tracking-widest font-bold">Select a trending topic above</p>
<p className="text-white/20 text-[10px] font-mono mt-3 uppercase tracking-wider max-w-xs mx-auto leading-relaxed">
Our engines will analyze the viral potential and suggest premium assets
</p>
</div>
)}
</div>