Complete overlay redesign with Windows Start Menu style

Features:
- Collapsible search bar with logo button
- Quick Access grid (6 plugin shortcuts)
- Pinned plugins section (with pin/unpin)
- All plugins list with search filter
- Toggle switches for each plugin
- Session timer display
- Keyboard shortcut hints
- Glassmorphism design with blur
- Smooth expand/collapse animations
- Proper styling with inline styles (no Tailwind dependency issues)
This commit is contained in:
Aether 2026-02-23 22:06:09 +00:00
parent 22230df27f
commit 3c5c7f2ed1
No known key found for this signature in database
GPG Key ID: 95AFEE837E39AFD2
3 changed files with 1006 additions and 387 deletions

257
IMPLEMENTATION_PLAN.md Normal file
View File

@ -0,0 +1,257 @@
# EU-Utility V3 Implementation Plan
Based on original Python/PyQt6 EU-Utility analysis.
---
## Original Features Analysis
### 1. Architecture
- **Original:** Python 3.11 + PyQt6
- **New:** Rust + Tauri + React + TypeScript
- **Plugin System:** 25+ built-in plugins
### 2. UI Structure
```
Main Overlay (Semi-transparent, always on top)
├── Left Sidebar: Plugin tabs/icons
├── Center: Active plugin content
└── Bottom: Quick actions bar
Floating Icon (Screen overlay)
├── Double-click: Toggle main overlay
├── Right-click: Context menu
└── Draggable position
```
### 3. Plugin Categories
#### Dashboard & Utility
- [ ] Dashboard - Customizable start page with stats
- [ ] Calculator - Standard calculator
- [ ] Settings - Preferences configuration
- [ ] Plugin Store - Community marketplace
#### Search & Information
- [ ] Universal Search - Nexus entities (items, mobs, locations)
- [ ] Nexus Search - Items and market data
- [ ] TP Runner - Teleporter route planner
#### Calculators
- [ ] DPP Calculator - Damage Per PEC, weapon efficiency
- [ ] Crafting Calc - Blueprint calculator with success rates
- [ ] Enhancer Calc - Break rates and costs
#### Trackers
- [ ] Loot Tracker - Hunting loot with ROI analysis
- [ ] Skill Scanner - OCR-based skill tracking
- [ ] Codex Tracker - Creature challenge progress
- [ ] Mission Tracker - Mission and objective tracking
- [ ] Global Tracker - Globals, HOFs, ATHs tracking
- [ ] Mining Helper - Claims and hotspot tracking
- [ ] Auction Tracker - Price and markup tracking
- [ ] Inventory Manager - TT value and item management
- [ ] Profession Scanner - Profession rank tracking
#### Game Integration
- [ ] Game Reader - OCR for in-game menus/text
- [ ] Chat Logger - Log, search, filter chat
#### External Integration
- [ ] Spotify Controller - Control playback
---
## Hotkey Mapping
| Hotkey | Action | Plugin |
|--------|--------|--------|
| Ctrl+Shift+U | Toggle main overlay | Global |
| Ctrl+Shift+H | Hide all overlays | Global |
| Ctrl+Shift+F | Universal Search | Search |
| Ctrl+Shift+N | Nexus Search | Search |
| Ctrl+Shift+C | Calculator | Utility |
| Ctrl+Shift+D | DPP Calculator | Calculator |
| Ctrl+Shift+E | Enhancer Calc | Calculator |
| Ctrl+Shift+B | Crafting Calc | Calculator |
| Ctrl+Shift+L | Loot Tracker | Tracker |
| Ctrl+Shift+S | Skill Scanner | Tracker |
| Ctrl+Shift+X | Codex Tracker | Tracker |
| Ctrl+Shift+R | Game Reader | Scanner |
| Ctrl+Shift+M | Spotify Controller | Media |
| Ctrl+Shift+Home | Dashboard | Overview |
| Ctrl+Shift+, | Settings | Configuration |
---
## New UI Design (Windows Start Menu Style)
### Main Overlay (Bottom Center)
```
┌─────────────────────────────────────────────────────────┐
│ [Logo] Search plugins, items... [Timer] [Expand] │
├─────────────────────────────────────────────────────────┤
│ │
│ Quick Access [Session: 02:34:12] │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │Calc│ │Loot│ │Skill│ │Price│ │Search│ │Settings│ │
│ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ │
│ │
│ Pinned Plugins │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ [Icon] Loot Tracker [On] [Pin] │
│ [Icon] Skill Scanner [On] [Pin] │
│ [Icon] DPP Calculator [Off] [Pin] │
│ │
│ All Plugins │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ [Icon] Codex Tracker [Off] [Pin] │
│ [Icon] Game Reader [Off] [Pin] │
│ [Icon] Mining Helper [Off] [Pin] │
│ │
├─────────────────────────────────────────────────────────┤
│ [Status: Online] Ctrl+Shift+U to toggle │
└─────────────────────────────────────────────────────────┘
```
### Design Elements
- **Background:** Dark glassmorphism (rgba(32, 32, 32, 0.95))
- **Blur:** 32px backdrop blur
- **Border:** 1px rgba(255, 255, 255, 0.08)
- **Shadow:** 0 8px 32px rgba(0, 0, 0, 0.4)
- **Border Radius:** 32px (collapsed), 12px (expanded)
- **Position:** Bottom center of screen
- **Width:** 640px (compact), 720px (expanded)
### Features
1. **Collapsible** - Click logo to expand/collapse
2. **Search** - Type to filter plugins instantly
3. **Pin System** - Pin favorite plugins to top
4. **Quick Access** - 6 shortcut icons always visible
5. **Plugin List** - All plugins with toggle switches
6. **Session Timer** - Live hunt duration
7. **Status Bar** - Online/offline indicator
---
## Implementation Phases
### Phase 1: Core Framework (COMPLETE)
- [x] Rust + Tauri setup
- [x] React + TypeScript frontend
- [x] Plugin system architecture
- [x] Event bus
- [x] Settings persistence
- [x] Hotkey system
- [x] Window management
### Phase 2: UI Redesign (IN PROGRESS)
- [ ] Windows Start Menu overlay
- [ ] Collapsible search bar
- [ ] Plugin grid/list view
- [ ] Pin/unpin functionality
- [ ] Quick access icons
- [ ] Session timer display
### Phase 3: Essential Plugins
- [ ] Dashboard
- [ ] Universal Search
- [ ] Calculator
- [ ] Loot Tracker
- [ ] Settings
### Phase 4: Advanced Plugins
- [ ] DPP Calculator
- [ ] Skill Scanner (OCR)
- [ ] Codex Tracker
- [ ] Game Reader (OCR)
### Phase 5: Full Feature Set
- [ ] All 25+ plugins
- [ ] Spotify integration
- [ ] Chat logger
- [ ] Plugin marketplace
---
## Technical Decisions
### Plugin System
```typescript
interface Plugin {
id: string;
name: string;
description: string;
category: 'dashboard' | 'search' | 'calculator' | 'tracker' | 'game' | 'media';
icon: string;
version: string;
author: string;
hotkey?: string;
pinned: boolean;
active: boolean;
component: React.ComponentType;
}
```
### Data Storage
- SQLite for local data
- Tauri settings API
- Plugin data isolation
### OCR Integration
- Tesseract for Windows (static link)
- EasyOCR fallback
- Screen region capture
- Text recognition pipeline
### Screen Reading
- Window capture (game window)
- Region selection (drag to define)
- OCR text extraction
- HP bar detection
- Coordinate reading
- Mob name recognition
---
## File Structure
```
src/
├── components/
│ ├── Layout.tsx
│ ├── Sidebar.tsx
│ ├── PluginGrid.tsx
│ ├── PluginList.tsx
│ ├── SearchBar.tsx
│ ├── QuickAccess.tsx
│ └── PinnedPlugins.tsx
├── plugins/
│ ├── dashboard/
│ ├── search/
│ ├── calculator/
│ ├── tracker/
│ └── game/
├── pages/
│ ├── Dashboard.tsx
│ ├── Plugins.tsx
│ ├── Settings.tsx
│ └── Overlay.tsx
└── store/
├── appStore.ts
└── pluginStore.ts
```
---
## Next Steps
1. **Implement new overlay UI** (Windows Start Menu style)
2. **Add pin/unpin functionality**
3. **Create plugin grid component**
4. **Build essential plugins** (Dashboard, Search, Calculator)
5. **Add OCR integration** for Game Reader
---
*Implementation Plan - EU-Utility V3*

View File

@ -18,29 +18,146 @@ import {
Maximize2,
Minimize2,
Grid,
Command
Command,
Pin,
PinOff,
MoreVertical
} from 'lucide-react'
interface MenuItem {
interface Plugin {
id: string
name: string
description: string
icon: React.ReactNode
label: string
description?: string
category: 'dashboard' | 'search' | 'calculator' | 'tracker' | 'game' | 'media' | 'utility'
hotkey?: string
onClick: () => void
category: 'tools' | 'plugins' | 'system'
pinned: boolean
active: boolean
onToggle: () => void
onPin: () => void
}
export default function Overlay() {
const [isVisible, setIsVisible] = useState(false)
const [isExpanded, setIsExpanded] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const [activePlugins, setActivePlugins] = useState<string[]>(['loot', 'skills'])
const [sessionTime, setSessionTime] = useState(0)
const [currentPED, setCurrentPED] = useState(0)
const inputRef = useRef<HTMLInputElement>(null)
// Plugin state
const [plugins, setPlugins] = useState<Plugin[]>([
{
id: 'dashboard',
name: 'Dashboard',
description: 'Overview and statistics',
icon: <LayoutDashboard className="w-5 h-5" />,
category: 'dashboard',
hotkey: 'Ctrl+Shift+Home',
pinned: true,
active: true,
onToggle: () => togglePlugin('dashboard'),
onPin: () => togglePin('dashboard')
},
{
id: 'search',
name: 'Universal Search',
description: 'Search items, mobs, locations',
icon: <Search className="w-5 h-5" />,
category: 'search',
hotkey: 'Ctrl+Shift+F',
pinned: true,
active: false,
onToggle: () => togglePlugin('search'),
onPin: () => togglePin('search')
},
{
id: 'calculator',
name: 'Calculator',
description: 'Standard calculator',
icon: <Calculator className="w-5 h-5" />,
category: 'calculator',
hotkey: 'Ctrl+Shift+C',
pinned: true,
active: false,
onToggle: () => togglePlugin('calculator'),
onPin: () => togglePin('calculator')
},
{
id: 'dpp-calc',
name: 'DPP Calculator',
description: 'Damage per PEC efficiency',
icon: <Zap className="w-5 h-5" />,
category: 'calculator',
hotkey: 'Ctrl+Shift+D',
pinned: false,
active: false,
onToggle: () => togglePlugin('dpp-calc'),
onPin: () => togglePin('dpp-calc')
},
{
id: 'loot-tracker',
name: 'Loot Tracker',
description: 'Track hunting sessions',
icon: <Target className="w-5 h-5" />,
category: 'tracker',
hotkey: 'Ctrl+Shift+L',
pinned: true,
active: false,
onToggle: () => togglePlugin('loot-tracker'),
onPin: () => togglePin('loot-tracker')
},
{
id: 'skill-scanner',
name: 'Skill Scanner',
description: 'OCR skill tracking',
icon: <TrendingUp className="w-5 h-5" />,
category: 'tracker',
hotkey: 'Ctrl+Shift+S',
pinned: false,
active: false,
onToggle: () => togglePlugin('skill-scanner'),
onPin: () => togglePin('skill-scanner')
},
{
id: 'codex-tracker',
name: 'Codex Tracker',
description: 'Creature challenges',
icon: <Grid className="w-5 h-5" />,
category: 'tracker',
hotkey: 'Ctrl+Shift+X',
pinned: false,
active: false,
onToggle: () => togglePlugin('codex-tracker'),
onPin: () => togglePin('codex-tracker')
},
{
id: 'game-reader',
name: 'Game Reader',
description: 'OCR text recognition',
icon: <BarChart3 className="w-5 h-5" />,
category: 'game',
hotkey: 'Ctrl+Shift+R',
pinned: false,
active: false,
onToggle: () => togglePlugin('game-reader'),
onPin: () => togglePin('game-reader')
},
{
id: 'settings',
name: 'Settings',
description: 'Configure EU-Utility',
icon: <Settings className="w-5 h-5" />,
category: 'utility',
hotkey: 'Ctrl+Shift+,',
pinned: false,
active: false,
onToggle: () => togglePlugin('settings'),
onPin: () => togglePin('settings')
}
])
useEffect(() => {
// Listen for overlay toggle
const unlisten = listen('overlay:toggle', () => {
setIsVisible(prev => {
const newVisible = !prev
@ -51,7 +168,6 @@ export default function Overlay() {
})
})
// Session timer
const timer = setInterval(() => {
setSessionTime(prev => prev + 1)
}, 1000)
@ -71,89 +187,44 @@ export default function Overlay() {
const formatTime = (seconds: number) => {
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = seconds % 60
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`
}
const menuItems: MenuItem[] = [
{
id: 'calculator',
icon: <Calculator className="w-5 h-5" />,
label: 'Calculator',
description: 'Quick calculations',
hotkey: 'Ctrl+Shift+C',
onClick: () => invoke('open_calculator'),
category: 'tools'
},
{
id: 'loot-tracker',
icon: <Target className="w-5 h-5" />,
label: 'Loot Tracker',
description: 'Track hunting sessions',
hotkey: 'Ctrl+Shift+L',
onClick: () => togglePlugin('loot'),
category: 'plugins'
},
{
id: 'skill-tracker',
icon: <TrendingUp className="w-5 h-5" />,
label: 'Skill Tracker',
description: 'Monitor skill gains',
onClick: () => togglePlugin('skills'),
category: 'plugins'
},
{
id: 'price-check',
icon: <BarChart3 className="w-5 h-5" />,
label: 'Price Check',
description: 'Item market values',
onClick: () => invoke('open_price_checker'),
category: 'tools'
},
{
id: 'dashboard',
icon: <LayoutDashboard className="w-5 h-5" />,
label: 'Dashboard',
description: 'Main application window',
onClick: () => invoke('show_main_window'),
category: 'system'
},
{
id: 'settings',
icon: <Settings className="w-5 h-5" />,
label: 'Settings',
description: 'Configure application',
onClick: () => invoke('show_settings_window'),
category: 'system'
},
]
const togglePlugin = (pluginId: string) => {
setActivePlugins(prev =>
prev.includes(pluginId)
? prev.filter(id => id !== pluginId)
: [...prev, pluginId]
)
const togglePlugin = (id: string) => {
setPlugins(prev => prev.map(p =>
p.id === id ? { ...p, active: !p.active } : p
))
}
const filteredItems = searchQuery
? menuItems.filter(item =>
item.label.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.description?.toLowerCase().includes(searchQuery.toLowerCase())
)
: menuItems
const togglePin = (id: string) => {
setPlugins(prev => prev.map(p =>
p.id === id ? { ...p, pinned: !p.pinned } : p
))
}
const groupedItems = filteredItems.reduce((acc, item) => {
if (!acc[item.category]) acc[item.category] = []
acc[item.category].push(item)
return acc
}, {} as Record<string, MenuItem[]>)
const filteredPlugins = searchQuery
? plugins.filter(p =>
p.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description.toLowerCase().includes(searchQuery.toLowerCase())
)
: plugins
const pinnedPlugins = filteredPlugins.filter(p => p.pinned)
const unpinnedPlugins = filteredPlugins.filter(p => !p.pinned)
const activeCount = plugins.filter(p => p.active).length
if (!isVisible) return null
return (
<div className="fixed inset-0 pointer-events-none flex items-end justify-center pb-6">
{/* Windows-Style Overlay Container */}
<div
className="fixed pointer-events-none flex items-end justify-center"
style={{
bottom: '40px',
left: 0,
right: 0,
zIndex: 9999
}}
>
<div
className="pointer-events-auto transition-all duration-300 ease-out"
style={{
@ -161,36 +232,36 @@ export default function Overlay() {
maxWidth: '90vw',
}}
>
{/* Main Search Bar - Windows Style */}
{/* Main Container */}
<div
className="relative overflow-hidden"
style={{
background: 'rgba(32, 32, 32, 0.95)',
background: 'rgba(32, 32, 32, 0.98)',
backdropFilter: 'blur(32px) saturate(180%)',
WebkitBackdropFilter: 'blur(32px) saturate(180%)',
borderRadius: isExpanded ? '12px' : '32px',
border: '1px solid rgba(255, 255, 255, 0.08)',
borderRadius: isExpanded ? '16px' : '32px',
border: '1px solid rgba(255, 255, 255, 0.1)',
boxShadow: isExpanded
? '0 32px 64px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.1)'
: '0 8px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.05)',
? '0 32px 64px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(99, 102, 241, 0.1)'
: '0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(99, 102, 241, 0.1)',
overflow: 'hidden',
}}
>
{/* Search Input Row */}
{/* Header / Search Bar */}
<div
className="flex items-center gap-3 px-5 h-14"
className="flex items-center gap-3 px-4 h-14"
data-tauri-drag-region
>
{/* Logo Icon - Windows Style */}
<div
className="flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center cursor-pointer transition-all hover:scale-105 active:scale-95"
{/* Logo Button */}
<button
onClick={() => setIsExpanded(!isExpanded)}
className="flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95"
style={{
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)',
boxShadow: '0 4px 12px rgba(99, 102, 241, 0.4)',
}}
onClick={() => setIsExpanded(!isExpanded)}
>
<Layers className="w-5 h-5 text-white" />
</div>
</button>
{/* Search Input */}
<div className="flex-1 relative">
@ -200,21 +271,26 @@ export default function Overlay() {
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onFocus={() => setIsExpanded(true)}
placeholder="Type to search plugins, tools..."
className="w-full bg-transparent border-none text-white text-base placeholder:text-white/40 focus:outline-none"
style={{ fontSize: '15px' }}
placeholder="Type to search plugins..."
className="w-full bg-transparent border-none text-white text-base focus:outline-none"
style={{
fontSize: '15px',
color: 'rgba(255, 255, 255, 0.9)'
}}
/>
</div>
{/* Right Side Actions */}
{/* Right Side */}
<div className="flex items-center gap-2">
{/* Session Timer */}
<div
className="flex items-center gap-2 px-3 py-1.5 rounded-full"
style={{ background: 'rgba(255, 255, 255, 0.06)' }}
style={{ background: 'rgba(255, 255, 255, 0.08)' }}
>
<Clock className="w-4 h-4 text-indigo-400" />
<span className="text-sm font-mono text-white/80">{formatTime(sessionTime)}</span>
<Clock className="w-4 h-4" style={{ color: '#818cf8' }} />
<span className="text-sm font-mono" style={{ color: 'rgba(255, 255, 255, 0.8)' }}>
{formatTime(sessionTime)}
</span>
</div>
{/* Expand/Collapse */}
@ -223,9 +299,9 @@ export default function Overlay() {
className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-white/10 transition-colors"
>
{isExpanded ? (
<Minimize2 className="w-4 h-4 text-white/60" />
<ChevronUp className="w-4 h-4" style={{ color: 'rgba(255, 255, 255, 0.6)' }} />
) : (
<Maximize2 className="w-4 h-4 text-white/60" />
<Maximize2 className="w-4 h-4" style={{ color: 'rgba(255, 255, 255, 0.6)' }} />
)}
</button>
@ -234,102 +310,233 @@ export default function Overlay() {
onClick={() => setIsVisible(false)}
className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-white/10 transition-colors"
>
<X className="w-4 h-4 text-white/60 hover:text-white" />
<X className="w-4 h-4" style={{ color: 'rgba(255, 255, 255, 0.6)' }} />
</button>
</div>
</div>
{/* Expanded Menu - Windows Start Menu Style */}
{/* Expanded Content */}
{isExpanded && (
<div
className="border-t border-white/10"
style={{
maxHeight: '480px',
overflow: 'hidden',
}}
className="border-t"
style={{ borderColor: 'rgba(255, 255, 255, 0.08)' }}
>
{/* Quick Actions Grid */}
{/* Quick Access */}
<div className="p-5">
<div className="flex items-center justify-between mb-4">
<span className="text-xs font-semibold uppercase tracking-wider text-white/40">Quick Access</span>
<div className="flex items-center gap-2">
<span className="text-xs text-white/30">{activePlugins.length} active</span>
</div>
<span
className="text-xs font-semibold uppercase tracking-wider"
style={{ color: 'rgba(255, 255, 255, 0.4)' }}
>
Quick Access
</span>
<span className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.3)' }}>
{activeCount} active
</span>
</div>
<div className="grid grid-cols-6 gap-2">
{menuItems.slice(0, 6).map((item) => (
{plugins.slice(0, 6).map((plugin) => (
<button
key={item.id}
key={plugin.id}
onClick={() => {
item.onClick()
if (!item.id.includes('tracker')) setIsVisible(false)
plugin.onToggle()
if (!plugin.active) setIsVisible(false)
}}
className="group flex flex-col items-center gap-2 p-3 rounded-xl transition-all hover:bg-white/10"
className="flex flex-col items-center gap-2 p-3 rounded-xl transition-all hover:bg-white/10"
>
<div
className={`w-12 h-12 rounded-xl flex items-center justify-center transition-all ${
activePlugins.includes(item.id) || item.id === 'loot' && activePlugins.includes('loot')
? 'bg-indigo-500/20 text-indigo-400 ring-1 ring-indigo-500/30'
: 'bg-white/5 text-white/60 group-hover:bg-white/10 group-hover:text-white'
}`}
className="w-12 h-12 rounded-xl flex items-center justify-center transition-all"
style={{
background: plugin.active
? 'rgba(99, 102, 241, 0.2)'
: 'rgba(255, 255, 255, 0.05)',
color: plugin.active ? '#818cf8' : 'rgba(255, 255, 255, 0.6)'
}}
>
{item.icon}
{plugin.icon}
</div>
<span className="text-[11px] text-white/70 text-center leading-tight">{item.label}</span>
<span
className="text-[11px] text-center leading-tight"
style={{ color: 'rgba(255, 255, 255, 0.7)' }}
>
{plugin.name}
</span>
</button>
))}
</div>
</div>
{/* Search Results or All Items */}
<div className="px-5 pb-5" style={{ maxHeight: '280px', overflowY: 'auto' }}>
{Object.entries(groupedItems).map(([category, items]) => (
<div key={category} className="mb-4 last:mb-0">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-semibold uppercase tracking-wider text-white/40">
{category === 'tools' ? 'Tools' : category === 'plugins' ? 'Plugins' : 'System'}
{/* Pinned Plugins */}
{pinnedPlugins.length > 0 && (
<div className="px-5 pb-4">
<div className="flex items-center justify-between mb-3">
<span
className="text-xs font-semibold uppercase tracking-wider"
style={{ color: 'rgba(255, 255, 255, 0.4)' }}
>
Pinned
</span>
</div>
<div className="space-y-1">
{items.map((item) => (
<button
key={item.id}
onClick={() => {
item.onClick()
if (!item.id.includes('tracker')) setIsVisible(false)
}}
className="w-full flex items-center gap-3 p-3 rounded-lg hover:bg-white/10 transition-all group"
{pinnedPlugins.map((plugin) => (
<div
key={plugin.id}
className="flex items-center justify-between p-3 rounded-lg hover:bg-white/5 transition-all cursor-pointer group"
onClick={plugin.onToggle}
>
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${
activePlugins.includes(item.id) || (item.id === 'loot-tracker' && activePlugins.includes('loot'))
? 'bg-indigo-500/20 text-indigo-400'
: 'bg-white/5 text-white/60 group-hover:text-white'
}`}>
{item.icon}
<div className="flex items-center gap-3">
<div
className="w-8 h-8 rounded-lg flex items-center justify-center"
style={{
background: plugin.active
? 'rgba(99, 102, 241, 0.2)'
: 'rgba(255, 255, 255, 0.05)',
color: plugin.active ? '#818cf8' : 'rgba(255, 255, 255, 0.5)'
}}
>
{plugin.icon}
</div>
<div className="flex-1 text-left">
<div className="text-sm font-medium text-white/90 group-hover:text-white">{item.label}</div>
{item.description && (
<div className="text-xs text-white/40">{item.description}</div>
)}
<div>
<div style={{ color: 'rgba(255, 255, 255, 0.9)' }} className="text-sm font-medium">
{plugin.name}
</div>
{item.hotkey && (
<kbd className="px-2 py-1 text-xs rounded bg-white/5 text-white/40 font-mono">
{item.hotkey}
<div style={{ color: 'rgba(255, 255, 255, 0.4)' }} className="text-xs">
{plugin.description}
</div>
</div>
</div>
<div className="flex items-center gap-2">
{plugin.hotkey && (
<kbd
className="px-2 py-1 text-xs rounded font-mono"
style={{
background: 'rgba(255, 255, 255, 0.05)',
color: 'rgba(255, 255, 255, 0.4)'
}}
>
{plugin.hotkey}
</kbd>
)}
{(activePlugins.includes(item.id) || (item.id === 'loot-tracker' && activePlugins.includes('loot'))) && (
<div className="w-2 h-2 rounded-full bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.5)]" />
)}
</button>
))}
</div>
</div>
))}
{filteredItems.length === 0 && (
<div className="text-center py-8 text-white/40">
<button
onClick={(e) => {
e.stopPropagation()
plugin.onPin()
}}
className="w-7 h-7 flex items-center justify-center rounded-lg hover:bg-white/10 transition-colors"
>
<Pin className="w-4 h-4" style={{ color: '#818cf8' }} />
</button>
<div
className="w-10 h-5 rounded-full relative cursor-pointer transition-colors"
style={{ background: plugin.active ? '#6366f1' : 'rgba(255, 255, 255, 0.2)' }}
onClick={(e) => {
e.stopPropagation()
plugin.onToggle()
}}
>
<div
className="absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all"
style={{ left: plugin.active ? '22px' : '2px' }}
/>
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* All Plugins */}
<div className="px-5 pb-5" style={{ maxHeight: '300px', overflowY: 'auto' }}>
<div className="flex items-center justify-between mb-3">
<span
className="text-xs font-semibold uppercase tracking-wider"
style={{ color: 'rgba(255, 255, 255, 0.4)' }}
>
All Plugins
</span>
</div>
<div className="space-y-1">
{unpinnedPlugins.map((plugin) => (
<div
key={plugin.id}
className="flex items-center justify-between p-3 rounded-lg hover:bg-white/5 transition-all cursor-pointer group"
onClick={plugin.onToggle}
>
<div className="flex items-center gap-3">
<div
className="w-8 h-8 rounded-lg flex items-center justify-center"
style={{
background: plugin.active
? 'rgba(99, 102, 241, 0.2)'
: 'rgba(255, 255, 255, 0.05)',
color: plugin.active ? '#818cf8' : 'rgba(255, 255, 255, 0.5)'
}}
>
{plugin.icon}
</div>
<div>
<div style={{ color: 'rgba(255, 255, 255, 0.9)' }} className="text-sm font-medium">
{plugin.name}
</div>
<div style={{ color: 'rgba(255, 255, 255, 0.4)' }} className="text-xs">
{plugin.description}
</div>
</div>
</div>
<div className="flex items-center gap-2">
{plugin.hotkey && (
<kbd
className="px-2 py-1 text-xs rounded font-mono"
style={{
background: 'rgba(255, 255, 255, 0.05)',
color: 'rgba(255, 255, 255, 0.4)'
}}
>
{plugin.hotkey}
</kbd>
)}
<button
onClick={(e) => {
e.stopPropagation()
plugin.onPin()
}}
className="w-7 h-7 flex items-center justify-center rounded-lg hover:bg-white/10 transition-colors opacity-0 group-hover:opacity-100"
>
<PinOff className="w-4 h-4" style={{ color: 'rgba(255, 255, 255, 0.4)' }} />
</button>
<div
className="w-10 h-5 rounded-full relative cursor-pointer transition-colors"
style={{ background: plugin.active ? '#6366f1' : 'rgba(255, 255, 255, 0.2)' }}
onClick={(e) => {
e.stopPropagation()
plugin.onToggle()
}}
>
<div
className="absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all"
style={{ left: plugin.active ? '22px' : '2px' }}
/>
</div>
</div>
</div>
))}
</div>
{filteredPlugins.length === 0 && (
<div
className="text-center py-8"
style={{ color: 'rgba(255, 255, 255, 0.4)' }}
>
<Search className="w-12 h-12 mx-auto mb-3 opacity-30" />
<p className="text-sm">No results found for "{searchQuery}"</p>
</div>
@ -338,17 +545,34 @@ export default function Overlay() {
{/* Footer */}
<div
className="flex items-center justify-between px-5 py-3 border-t border-white/10"
style={{ background: 'rgba(0, 0, 0, 0.3)' }}
className="flex items-center justify-between px-5 py-3 border-t"
style={{
borderColor: 'rgba(255, 255, 255, 0.08)',
background: 'rgba(0, 0, 0, 0.3)'
}}
>
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
<span className="text-xs text-white/50">EU-Utility V3.0</span>
<div
className="w-2 h-2 rounded-full animate-pulse"
style={{ background: '#10b981', boxShadow: '0 0 8px rgba(16, 185, 129, 0.5)' }}
/>
<span className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.5)' }}>
EU-Utility V3.0
</span>
</div>
<div className="flex items-center gap-1 text-xs text-white/40">
<div
className="flex items-center gap-1 text-xs"
style={{ color: 'rgba(255, 255, 255, 0.4)' }}
>
<Command className="w-3 h-3" />
<span>Press</span>
<kbd className="px-1.5 py-0.5 rounded bg-white/10 text-white/60">Ctrl+Shift+U</kbd>
<kbd
className="px-1.5 py-0.5 rounded"
style={{ background: 'rgba(255, 255, 255, 0.1)' }}
>
Ctrl+Shift+U
</kbd>
<span>to toggle</span>
</div>
</div>

View File

@ -6,6 +6,24 @@
@tailwind components;
@tailwind utilities;
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #13131a;
--bg-card: #1a1a24;
--bg-hover: #252530;
--text-primary: #ffffff;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--accent-primary: #6366f1;
--accent-secondary: #8b5cf6;
--accent-gradient: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
--border-color: rgba(255, 255, 255, 0.08);
--glass-bg: rgba(19, 19, 26, 0.95);
}
* {
margin: 0;
padding: 0;
@ -18,249 +36,310 @@ html, body, #root {
}
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background: linear-gradient(135deg, #0a0a0f 0%, #13131a 50%, #0a0a0f 100%);
color: #e2e8f0;
overflow: hidden;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Custom scrollbar */
/* Scrollbar */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: rgba(30, 30, 46, 0.5);
background: var(--bg-secondary);
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: rgba(99, 102, 241, 0.4);
background: var(--accent-primary);
border-radius: 3px;
opacity: 0.5;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(99, 102, 241, 0.6);
opacity: 1;
}
/* Selection */
::selection {
background: rgba(99, 102, 241, 0.3);
color: #ffffff;
}
/* Focus styles */
*:focus {
outline: none;
}
*:focus-visible {
outline: 2px solid rgba(99, 102, 241, 0.5);
outline-offset: 2px;
}
/* Animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse-glow {
0%, 100% {
box-shadow: 0 0 20px rgba(99, 102, 241, 0.2);
}
50% {
box-shadow: 0 0 40px rgba(99, 102, 241, 0.4);
}
}
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-5px);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.animate-fade-in {
animation: fadeIn 0.3s ease-out;
}
.animate-slide-in {
animation: slideIn 0.3s ease-out;
.animate-slide-up {
animation: slideUp 0.4s ease-out;
}
.animate-pulse-glow {
animation: pulse-glow 2s ease-in-out infinite;
.animate-pulse {
animation: pulse 2s ease-in-out infinite;
}
.animate-float {
animation: float 3s ease-in-out infinite;
/* Layout */
.min-h-screen {
min-height: 100vh;
}
/* Glassmorphism utilities */
.glass {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
.flex {
display: flex;
}
.glass-dark {
background: rgba(10, 10, 15, 0.8);
backdrop-filter: blur(20px);
border: 1px solid rgba(99, 102, 241, 0.2);
.flex-col {
flex-direction: column;
}
/* Gradient text */
.text-gradient {
background: linear-gradient(135deg, #6366f1, #a855f7, #ec4899);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
.items-center {
align-items: center;
}
/* Button styles */
.btn-primary {
background: linear-gradient(135deg, #6366f1, #4f46e5);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
.gap-2 {
gap: 0.5rem;
}
.gap-4 {
gap: 1rem;
}
.gap-6 {
gap: 1.5rem;
}
.p-4 {
padding: 1rem;
}
.p-6 {
padding: 1.5rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
/* Typography */
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
font-weight: 700;
}
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
font-weight: 600;
}
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-xs {
font-size: 0.75rem;
line-height: 1rem;
}
.font-mono {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.font-semibold {
font-weight: 600;
}
.font-bold {
font-weight: 700;
}
/* Colors */
.text-white {
color: var(--text-primary);
}
.text-gray-400 {
color: var(--text-secondary);
}
.text-gray-500 {
color: var(--text-muted);
}
.text-indigo-400 {
color: #818cf8;
}
.text-emerald-400 {
color: #34d399;
}
/* Backgrounds */
.bg-primary {
background: var(--bg-primary);
}
.bg-secondary {
background: var(--bg-secondary);
}
.bg-card {
background: var(--bg-card);
}
.bg-accent {
background: var(--accent-gradient);
}
/* Cards */
.card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.5rem;
transition: all 0.2s ease;
}
.card:hover {
border-color: rgba(99, 102, 241, 0.3);
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.625rem 1.25rem;
border-radius: 8px;
font-weight: 500;
transition: all 0.2s;
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);
font-size: 0.875rem;
transition: all 0.2s ease;
cursor: pointer;
border: none;
}
.btn-primary {
background: var(--accent-gradient);
color: white;
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
}
.btn-primary:active {
transform: translateY(0);
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.5);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.05);
color: #e2e8f0;
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.2s;
background: var(--bg-hover);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(99, 102, 241, 0.3);
border-color: rgba(99, 102, 241, 0.5);
}
/* Card styles */
.card {
background: rgba(19, 19, 26, 0.8);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12px;
transition: all 0.2s;
}
.card:hover {
border-color: rgba(99, 102, 241, 0.2);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
/* Input styles */
/* Inputs */
input, select, textarea {
width: 100%;
padding: 0.625rem 0.875rem;
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.5rem;
color: #e2e8f0;
padding: 0.5rem 0.75rem;
transition: all 0.2s;
border: 1px solid var(--border-color);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.875rem;
transition: all 0.2s ease;
}
input:focus, select:focus, textarea:focus {
border-color: rgba(99, 102, 241, 0.5);
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
input::placeholder {
color: rgba(148, 163, 184, 0.5);
color: var(--text-muted);
}
/* Range input */
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
outline: none;
/* Grid */
.grid {
display: grid;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: linear-gradient(135deg, #6366f1, #4f46e5);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 10px rgba(99, 102, 241, 0.4);
transition: transform 0.2s;
.grid-cols-2 {
grid-template-columns: repeat(2, 1fr);
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
.grid-cols-3 {
grid-template-columns: repeat(3, 1fr);
}
/* Toggle switch */
.toggle {
appearance: none;
width: 44px;
height: 24px;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
position: relative;
cursor: pointer;
transition: background 0.2s;
.grid-cols-4 {
grid-template-columns: repeat(4, 1fr);
}
.toggle::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
top: 2px;
left: 2px;
transition: transform 0.2s;
.gap-4 {
gap: 1rem;
}
.toggle:checked {
background: #6366f1;
/* Icons */
.icon {
width: 1.25rem;
height: 1.25rem;
}
.toggle:checked::after {
transform: translateX(20px);
.icon-sm {
width: 1rem;
height: 1rem;
}
.icon-lg {
width: 1.5rem;
height: 1.5rem;
}
/* Status indicators */
@ -268,74 +347,133 @@ input[type="range"]::-webkit-slider-thumb:hover {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
.status-dot.online {
background: #10b981;
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
box-shadow: 0 0 8px rgba(16, 185, 129, 0.5);
}
.status-dot.offline {
background: #64748b;
background: var(--text-muted);
}
.status-dot.warning {
background: #f59e0b;
box-shadow: 0 0 10px rgba(245, 158, 11, 0.5);
/* Glass effect */
.glass {
background: var(--glass-bg);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.status-dot.error {
background: #ef4444;
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
/* Sidebar */
.sidebar {
width: 260px;
background: var(--bg-secondary);
border-right: 1px solid var(--border-color);
padding: 1.5rem;
}
/* Tooltip */
.tooltip {
position: relative;
.sidebar-link {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 8px;
color: var(--text-secondary);
text-decoration: none;
transition: all 0.2s ease;
}
.tooltip::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
.sidebar-link:hover,
.sidebar-link.active {
background: rgba(99, 102, 241, 0.1);
color: var(--text-primary);
}
.sidebar-link.active {
background: rgba(99, 102, 241, 0.15);
}
/* Main content */
.main-content {
flex: 1;
padding: 2rem;
overflow-y: auto;
}
/* Header */
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 2rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--border-color);
}
/* Utility classes */
.rounded-lg {
border-radius: 8px;
}
.rounded-xl {
border-radius: 12px;
}
.rounded-full {
border-radius: 9999px;
}
.shadow-lg {
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.4);
}
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
/* Overlay specific */
.overlay-container {
position: fixed;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
padding: 0.25rem 0.5rem;
background: rgba(0, 0, 0, 0.9);
color: white;
font-size: 0.75rem;
border-radius: 0.25rem;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
z-index: 9999;
}
.tooltip:hover::after {
opacity: 1;
.overlay-bar {
background: rgba(32, 32, 32, 0.95);
backdrop-filter: blur(32px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 32px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
padding: 8px 16px;
display: flex;
align-items: center;
gap: 12px;
}
/* Draggable regions */
[data-tauri-drag-region] {
cursor: move;
user-select: none;
/* Responsive */
@media (max-width: 768px) {
.sidebar {
width: 100%;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
}
/* Line clamp */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
.main-content {
padding-bottom: 100px;
}
/* Hide scrollbar but keep functionality */
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
.grid-cols-4 {
grid-template-columns: repeat(2, 1fr);
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}