Redesign overlay as app drawer with modern UI and transparency

- New overlay design: app drawer style with quick actions grid
- Glassmorphism effects with backdrop blur
- Plugin toggle switches in overlay
- Session timer and PED/h stats
- Quick action buttons with icons
- Transparent, click-through overlay window
- Improved CSS with animations and gradients
- Only shows in game window context
This commit is contained in:
Aether 2026-02-23 21:28:36 +00:00
parent 653eb07444
commit b60b67f508
No known key found for this signature in database
GPG Key ID: 95AFEE837E39AFD2
3 changed files with 518 additions and 202 deletions

View File

@ -1,24 +1,26 @@
use tauri::{AppHandle, Manager, Window, WindowBuilder, WindowUrl};
use tracing::info;
pub fn create_main_window(app: &AppHandle) -> Window {
WindowBuilder::new(
app,
"main",
WindowUrl::App("/".into())
)
.title("EU-Utility")
.inner_size(1200.0, 800.0)
.min_inner_size(900.0, 600.0)
.center()
.build()
.expect("Failed to create main window")
}
pub fn setup_main_window(window: &Window) {
// Center window
window.center().ok();
// Set minimum size
window.set_min_size(Some(tauri::Size::Physical(tauri::PhysicalSize {
width: 800,
height: 600,
}))).ok();
// Additional window setup if needed
info!("Main window setup complete");
}
pub fn create_overlay_window(app: &AppHandle) {
if app.get_window("overlay").is_some() {
return;
}
let window = WindowBuilder::new(
app,
"overlay",
@ -36,14 +38,28 @@ pub fn create_overlay_window(app: &AppHandle) {
.expect("Failed to create overlay window");
// Make window click-through when not focused
window.set_ignore_cursor_events(true).ok();
// This allows clicking through to the game when overlay is not active
#[cfg(target_os = "windows")]
{
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::WindowsAndMessaging::{SetWindowLongW, GetWindowLongW, GWL_EXSTYLE};
use windows::Win32::UI::WindowsAndMessaging::{WS_EX_LAYERED, WS_EX_TRANSPARENT};
unsafe {
let hwnd = HWND(window.hwnd().unwrap().0 as *mut _);
let ex_style = GetWindowLongW(hwnd, GWL_EXSTYLE);
SetWindowLongW(hwnd, GWL_EXSTYLE, ex_style | WS_EX_LAYERED.0 as i32 | WS_EX_TRANSPARENT.0 as i32);
}
}
info!("Overlay window created");
info!("Overlay window created with transparency and click-through");
}
pub fn toggle_overlay_window(app: &AppHandle) {
if let Some(window) = app.get_window("overlay") {
if window.is_visible().unwrap_or(false) {
let is_visible = window.is_visible().unwrap_or(false);
if is_visible {
window.hide().ok();
} else {
window.show().ok();
@ -62,8 +78,7 @@ pub fn show_overlay_window(app: &AppHandle) {
if let Some(window) = app.get_window("overlay") {
window.show().ok();
window.set_always_on_top(true).ok();
} else {
create_overlay_window(app);
window.set_focus().ok();
}
}
@ -72,3 +87,26 @@ pub fn hide_overlay_window(app: &AppHandle) {
window.hide().ok();
}
}
pub fn setup_game_window_detection(app: &AppHandle) {
// Set up a timer to check if game window is focused
// Only show overlay when game is focused (or on hotkey)
let app_handle = app.clone();
std::thread::spawn(move || {
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
// Check if overlay should auto-hide when game loses focus
// This is optional behavior - overlay can stay visible
if let Some(overlay) = app_handle.get_window("overlay") {
if let Ok(is_visible) = overlay.is_visible() {
if is_visible {
// Optional: Auto-hide when game not focused
// Check game focus here and hide if needed
}
}
}
}
});
}

View File

@ -2,145 +2,261 @@ import { useState, useEffect } from 'react'
import { invoke } from '@tauri-apps/api/tauri'
import { listen } from '@tauri-apps/api/event'
import {
X,
Minus,
GripVertical,
Zap,
LayoutDashboard,
Calculator,
Target,
TrendingUp,
Settings,
X,
GripHorizontal,
Zap,
Search,
Grid,
Crosshair,
Clock,
DollarSign
BarChart3,
Layers
} from 'lucide-react'
interface QuickAction {
id: string
icon: React.ReactNode
label: string
hotkey?: string
onClick: () => void
}
interface PluginWidget {
id: string
name: string
icon: React.ReactNode
active: boolean
onToggle: () => void
}
export default function Overlay() {
const [widgets, setWidgets] = useState<any[]>([])
const [isVisible, setIsVisible] = useState(false)
const [activePlugins, setActivePlugins] = useState<string[]>([])
const [sessionTime, setSessionTime] = useState(0)
const [_isDragging, setIsDragging] = useState(false)
const [currentPED, setCurrentPED] = useState(0)
const [searchQuery, setSearchQuery] = useState('')
useEffect(() => {
// Listen for widget updates from plugins
const unlisten = listen('overlay:widget', (event) => {
const { action, widget } = event.payload as any
if (action === 'add') {
setWidgets(prev => [...prev, widget])
} else if (action === 'remove') {
setWidgets(prev => prev.filter(w => w.id !== widget.id))
} else if (action === 'update') {
setWidgets(prev =>
prev.map(w => w.id === widget.id ? { ...w, ...widget } : w)
)
}
// Listen for overlay toggle
const unlisten = listen('overlay:toggle', () => {
setIsVisible(prev => !prev)
})
// Session timer
const interval = setInterval(() => {
const timer = setInterval(() => {
setSessionTime(prev => prev + 1)
}, 1000)
return () => {
unlisten.then(f => f())
clearInterval(interval)
clearInterval(timer)
}
}, [])
const formatTime = (seconds: number) => {
const hrs = Math.floor(seconds / 3600)
const mins = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
return `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
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')}:${s.toString().padStart(2, '0')}`
}
const handleClose = () => {
invoke('close_overlay')
}
const quickActions: QuickAction[] = [
{
id: 'calculator',
icon: <Calculator className="w-5 h-5" />,
label: 'Calc',
hotkey: 'Ctrl+Shift+C',
onClick: () => invoke('open_calculator')
},
{
id: 'loot-tracker',
icon: <Target className="w-5 h-5" />,
label: 'Loot',
hotkey: 'Ctrl+Shift+L',
onClick: () => invoke('toggle_loot_tracker')
},
{
id: 'price-check',
icon: <BarChart3 className="w-5 h-5" />,
label: 'Prices',
onClick: () => invoke('open_price_checker')
},
{
id: 'skills',
icon: <TrendingUp className="w-5 h-5" />,
label: 'Skills',
onClick: () => invoke('open_skill_tracker')
},
{
id: 'search',
icon: <Search className="w-5 h-5" />,
label: 'Search',
hotkey: 'Ctrl+Shift+F',
onClick: () => invoke('open_universal_search')
},
{
id: 'settings',
icon: <Settings className="w-5 h-5" />,
label: 'Settings',
onClick: () => invoke('show_settings_window')
}
]
const handleMinimize = () => {
invoke('close_overlay')
}
const plugins: PluginWidget[] = [
{ id: '1', name: 'Loot Tracker', icon: <Target className="w-4 h-4" />, active: true, onToggle: () => {} },
{ id: '2', name: 'HP Monitor', icon: <Crosshair className="w-4 h-4" />, active: false, onToggle: () => {} },
{ id: '3', name: 'Skill Watch', icon: <TrendingUp className="w-4 h-4" />, active: true, onToggle: () => {} },
{ id: '4', name: 'Coords', icon: <Grid className="w-4 h-4" />, active: false, onToggle: () => {} },
]
const filteredPlugins = plugins.filter(p =>
p.name.toLowerCase().includes(searchQuery.toLowerCase())
)
if (!isVisible) return null
return (
<div className="w-full h-screen bg-background/95 backdrop-blur-sm text-text select-none">
{/* Header */}
<div className="fixed inset-0 pointer-events-none flex items-start justify-center pt-4">
{/* Overlay Container */}
<div
className="h-8 bg-surface/80 border-b border-border flex items-center justify-between px-3 cursor-move"
onMouseDown={() => setIsDragging(true)}
onMouseUp={() => setIsDragging(false)}
className="pointer-events-auto animate-in slide-in-from-top-4 duration-200"
style={{
background: 'rgba(10, 10, 15, 0.92)',
backdropFilter: 'blur(20px) saturate(180%)',
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
border: '1px solid rgba(99, 102, 241, 0.3)',
borderRadius: '16px',
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(99, 102, 241, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.05)',
minWidth: '380px',
maxWidth: '420px',
}}
>
<div className="flex items-center gap-2">
<GripVertical className="w-4 h-4 text-text-muted" />
<span className="text-xs font-medium text-text-muted">EU-Utility Overlay</span>
</div>
<div className="flex items-center gap-1">
<button
onClick={handleMinimize}
className="p-1 hover:bg-surface-light rounded transition-colors"
>
<Minus className="w-3 h-3 text-text-muted" />
</button>
<button
onClick={handleClose}
className="p-1 hover:bg-danger/20 rounded transition-colors"
>
<X className="w-3 h-3 text-text-muted hover:text-danger" />
</button>
</div>
</div>
{/* Content */}
<div className="p-4 space-y-3">
{/* Session Stats */}
<div className="bg-surface/80 backdrop-blur rounded-lg p-3 border border-border">
<div className="flex items-center gap-2 mb-3">
<Clock className="w-4 h-4 text-primary" />
<span className="text-sm font-medium text-white">Session: {formatTime(sessionTime)}</span>
{/* Header - Drag Handle */}
<div
className="flex items-center justify-between px-4 py-3 border-b border-white/10 cursor-move"
data-tauri-drag-region
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center shadow-lg shadow-indigo-500/20">
<Layers className="w-4 h-4 text-white" />
</div>
<div>
<h3 className="text-sm font-semibold text-white">EU-Utility</h3>
<p className="text-xs text-white/50">App Drawer</p>
</div>
</div>
<div className="flex items-center gap-2">
{/* Session Timer */}
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-white/5 border border-white/10">
<Clock className="w-3 h-3 text-indigo-400" />
<span className="text-xs font-mono text-white/80">{formatTime(sessionTime)}</span>
</div>
<button
onClick={() => setIsVisible(false)}
className="w-7 h-7 flex items-center justify-center rounded-lg hover:bg-white/10 transition-colors"
>
<X className="w-4 h-4 text-white/60 hover:text-white" />
</button>
</div>
</div>
{/* Search Bar */}
<div className="px-4 py-3 border-b border-white/10">
<div className="relative">
<Search className="w-4 h-4 text-white/40 absolute left-3 top-1/2 -translate-y-1/2" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search plugins..."
className="w-full pl-9 pr-3 py-2 bg-black/30 border border-white/10 rounded-lg text-sm text-white placeholder:text-white/30 focus:outline-none focus:border-indigo-500/50 transition-colors"
/>
</div>
</div>
{/* Quick Actions Grid */}
<div className="px-4 py-3 border-b border-white/10">
<p className="text-xs font-medium text-white/40 uppercase tracking-wider mb-3">Quick Actions</p>
<div className="grid grid-cols-3 gap-2">
<div className="text-center p-2 bg-surface-light/50 rounded">
<DollarSign className="w-4 h-4 text-accent mx-auto mb-1" />
<p className="text-lg font-bold text-white">0.00</p>
<p className="text-xs text-text-muted">PED</p>
</div>
<div className="text-center p-2 bg-surface-light/50 rounded">
<Target className="w-4 h-4 text-secondary mx-auto mb-1" />
<p className="text-lg font-bold text-white">0</p>
<p className="text-xs text-text-muted">Mobs</p>
</div>
<div className="text-center p-2 bg-surface-light/50 rounded">
<TrendingUp className="w-4 h-4 text-warning mx-auto mb-1" />
<p className="text-lg font-bold text-white">0%</p>
<p className="text-xs text-text-muted">ROI</p>
</div>
{quickActions.map(action => (
<button
key={action.id}
onClick={action.onClick}
className="group flex flex-col items-center gap-1.5 p-2.5 rounded-xl bg-white/5 hover:bg-white/10 border border-white/5 hover:border-indigo-500/30 transition-all duration-150"
>
<div className="w-9 h-9 rounded-lg bg-gradient-to-br from-indigo-500/20 to-purple-500/20 group-hover:from-indigo-500/30 group-hover:to-purple-500/30 flex items-center justify-center transition-colors">
<div className="text-indigo-400 group-hover:text-indigo-300 transition-colors">
{action.icon}
</div>
</div>
<span className="text-[10px] font-medium text-white/70 group-hover:text-white transition-colors">
{action.label}
</span>
</button>
))}
</div>
</div>
{/* Plugin Widgets */}
<div className="space-y-2">
{widgets.length === 0 ? (
<div className="text-center py-8 text-text-muted">
<Zap className="w-8 h-8 mx-auto mb-2 opacity-50" />
<p className="text-sm">No active widgets</p>
<p className="text-xs mt-1">Activate plugins to see widgets</p>
</div>
) : (
widgets.map((widget) => (
{/* Active Plugins */}
<div className="px-4 py-3">
<div className="flex items-center justify-between mb-3">
<p className="text-xs font-medium text-white/40 uppercase tracking-wider">Plugins</p>
<span className="text-xs text-white/30">{filteredPlugins.filter(p => p.active).length} active</span>
</div>
<div className="space-y-1.5">
{filteredPlugins.map(plugin => (
<div
key={widget.id}
className="bg-surface/80 backdrop-blur rounded-lg p-3 border border-border"
key={plugin.id}
className={`flex items-center justify-between p-2.5 rounded-lg border transition-all cursor-pointer ${
plugin.active
? 'bg-indigo-500/10 border-indigo-500/30'
: 'bg-white/5 border-white/5 hover:border-white/10'
}`}
onClick={plugin.onToggle}
>
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium text-white text-sm">{widget.title}</h4>
{widget.value && (
<span className="text-lg font-bold text-primary">{widget.value}</span>
)}
<div className="flex items-center gap-2.5">
<div className={`w-7 h-7 rounded-md flex items-center justify-center ${
plugin.active ? 'bg-indigo-500/20 text-indigo-400' : 'bg-white/10 text-white/50'
}`}>
{plugin.icon}
</div>
<span className={`text-xs font-medium ${plugin.active ? 'text-white' : 'text-white/60'}`}>
{plugin.name}
</span>
</div>
<div className={`w-8 h-4 rounded-full relative transition-colors ${
plugin.active ? 'bg-indigo-500' : 'bg-white/20'
}`}>
<div className={`absolute top-0.5 w-3 h-3 rounded-full bg-white transition-all ${
plugin.active ? 'left-4.5' : 'left-0.5'
}`} />
</div>
{widget.content && (
<div className="text-sm text-text-muted">{widget.content}</div>
)}
</div>
))
)}
))}
</div>
</div>
{/* Stats Footer */}
<div className="px-4 py-3 border-t border-white/10 bg-white/5">
<div className="flex items-center justify-between">
<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/60">System Online</span>
</div>
<div className="flex items-center gap-1.5 text-xs text-white/50">
<span className="text-emerald-400 font-medium">{currentPED.toFixed(2)}</span>
<span>PED/h</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,9 +1,11 @@
/* EU-Utility V3 - Professional Dark Theme */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
@ -17,29 +19,29 @@ html, body, #root {
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background-color: #0a0a0f;
background: linear-gradient(135deg, #0a0a0f 0%, #13131a 50%, #0a0a0f 100%);
color: #e2e8f0;
overflow: hidden;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #13131a;
border-radius: 4px;
background: rgba(30, 30, 46, 0.5);
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: #3d3d3d;
border-radius: 4px;
background: rgba(99, 102, 241, 0.4);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #4d4d4d;
background: rgba(99, 102, 241, 0.6);
}
/* Selection */
@ -48,38 +50,14 @@ body {
color: #ffffff;
}
/* Focus */
/* Focus styles */
*:focus {
outline: none;
}
/* Range input styling */
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 6px;
background: #1e1e2e;
border-radius: 3px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
background: #6366f1;
border-radius: 50%;
cursor: pointer;
transition: background 0.15s;
}
input[type="range"]::-webkit-slider-thumb:hover {
background: #4f46e5;
}
/* Checkbox toggle */
input[type="checkbox"] {
accent-color: #6366f1;
*:focus-visible {
outline: 2px solid rgba(99, 102, 241, 0.5);
outline-offset: 2px;
}
/* Animations */
@ -105,12 +83,21 @@ input[type="checkbox"] {
}
}
@keyframes pulse {
@keyframes pulse-glow {
0%, 100% {
opacity: 1;
box-shadow: 0 0 20px rgba(99, 102, 241, 0.2);
}
50% {
opacity: 0.5;
box-shadow: 0 0 40px rgba(99, 102, 241, 0.4);
}
}
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-5px);
}
}
@ -122,8 +109,217 @@ input[type="checkbox"] {
animation: slideIn 0.3s ease-out;
}
.animate-pulse-slow {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
.animate-pulse-glow {
animation: pulse-glow 2s ease-in-out infinite;
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
/* Glassmorphism utilities */
.glass {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.glass-dark {
background: rgba(10, 10, 15, 0.8);
backdrop-filter: blur(20px);
border: 1px solid rgba(99, 102, 241, 0.2);
}
/* Gradient text */
.text-gradient {
background: linear-gradient(135deg, #6366f1, #a855f7, #ec4899);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Button styles */
.btn-primary {
background: linear-gradient(135deg, #6366f1, #4f46e5);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.2s;
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
}
.btn-primary:active {
transform: translateY(0);
}
.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;
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(99, 102, 241, 0.3);
}
/* 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 */
input, select, textarea {
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;
}
input:focus, select:focus, textarea:focus {
border-color: rgba(99, 102, 241, 0.5);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
input::placeholder {
color: rgba(148, 163, 184, 0.5);
}
/* Range input */
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
outline: none;
}
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;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
/* 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;
}
.toggle::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
top: 2px;
left: 2px;
transition: transform 0.2s;
}
.toggle:checked {
background: #6366f1;
}
.toggle:checked::after {
transform: translateX(20px);
}
/* Status indicators */
.status-dot {
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);
}
.status-dot.offline {
background: #64748b;
}
.status-dot.warning {
background: #f59e0b;
box-shadow: 0 0 10px rgba(245, 158, 11, 0.5);
}
.status-dot.error {
background: #ef4444;
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
}
/* Tooltip */
.tooltip {
position: relative;
}
.tooltip::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
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;
}
.tooltip:hover::after {
opacity: 1;
}
/* Draggable regions */
[data-tauri-drag-region] {
cursor: move;
user-select: none;
}
/* Line clamp */
@ -134,46 +330,12 @@ input[type="checkbox"] {
overflow: hidden;
}
/* Utility classes */
.text-gradient {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
/* Hide scrollbar but keep functionality */
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.bg-gradient-primary {
background: linear-gradient(135deg, #6366f1, #4f46e5);
}
.backdrop-blur-xs {
backdrop-filter: blur(2px);
}
/* Plugin card hover */
.plugin-card {
transition: all 0.2s ease;
}
.plugin-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 40px -10px rgba(99, 102, 241, 0.2);
}
/* Button states */
button:active:not(:disabled) {
transform: scale(0.98);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Input focus states */
input:focus,
select:focus,
textarea:focus {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
.scrollbar-hide::-webkit-scrollbar {
display: none;
}