feat: Discord-style glass morphism UI redesign + nightly CI/CD

- App shell: gradient title, glass admin modal, avatar, admin login/logout
- All plugin empty states: floating icon animations, updated typography
- Soundboard: orange accent theme replacing blurple default
- Global styles: glass morphism variables, Discord-dark color palette
- CI/CD: nightly deploy (stops main, starts nightly on port 8085) + manual restore-main job

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-09 02:12:02 +01:00
parent ecd5e96ee2
commit 8abe0775a5
8 changed files with 556 additions and 94 deletions

View file

@ -40,6 +40,12 @@ export default function App() {
const [showVersionModal, setShowVersionModal] = useState(false);
const [pluginData, setPluginData] = useState<Record<string, any>>({});
// Admin state
const [adminLoggedIn, setAdminLoggedIn] = useState(false);
const [showAdminModal, setShowAdminModal] = useState(false);
const [adminPassword, setAdminPassword] = useState('');
const [adminError, setAdminError] = useState('');
// Electron auto-update state
const isElectron = !!(window as any).electronAPI?.isElectron;
const electronVersion = isElectron ? (window as any).electronAPI.version : null;
@ -130,13 +136,43 @@ export default function App() {
const version = (import.meta as any).env?.VITE_APP_VERSION ?? 'dev';
// Close version modal on Escape
// Close modals on Escape
useEffect(() => {
if (!showVersionModal) return;
const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') setShowVersionModal(false); };
if (!showVersionModal && !showAdminModal) return;
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setShowVersionModal(false);
setShowAdminModal(false);
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [showVersionModal]);
}, [showVersionModal, showAdminModal]);
// Admin login handler
const handleAdminLogin = () => {
if (!adminPassword) return;
fetch('/api/admin/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: adminPassword }),
})
.then(r => {
if (r.ok) {
setAdminLoggedIn(true);
setAdminPassword('');
setAdminError('');
} else {
setAdminError('Falsches Passwort');
}
})
.catch(() => setAdminError('Verbindungsfehler'));
};
const handleAdminLogout = () => {
setAdminLoggedIn(false);
setShowAdminModal(false);
};
// Tab icon mapping
@ -198,7 +234,6 @@ export default function App() {
<span
className="hub-version hub-version-clickable"
onClick={() => {
// Status vom Main-Prozess synchronisieren bevor Modal öffnet
if (isElectron) {
const api = (window as any).electronAPI;
const s = api.getUpdateStatus?.();
@ -212,6 +247,15 @@ export default function App() {
>
v{version}
</span>
<button
className={`hub-admin-btn ${adminLoggedIn ? 'logged-in' : ''}`}
onClick={() => setShowAdminModal(true)}
title="Admin Login"
>
{'\u{1F511}'}
{adminLoggedIn && <span className="hub-admin-green-dot" />}
</button>
<div className="hub-avatar">DK</div>
</div>
</header>
@ -307,6 +351,46 @@ export default function App() {
</div>
)}
{showAdminModal && (
<div className="hub-admin-overlay" onClick={() => setShowAdminModal(false)}>
<div className="hub-admin-modal" onClick={e => e.stopPropagation()}>
{adminLoggedIn ? (
<>
<div className="hub-admin-modal-title">Admin Panel</div>
<div className="hub-admin-modal-info">
<div className="hub-admin-modal-avatar">A</div>
<div className="hub-admin-modal-text">
<span className="hub-admin-modal-name">Administrator</span>
<span className="hub-admin-modal-role">Eingeloggt</span>
</div>
</div>
<button className="hub-admin-modal-logout" onClick={handleAdminLogout}>
Ausloggen
</button>
</>
) : (
<>
<div className="hub-admin-modal-title">{'\u{1F511}'} Admin Login</div>
<div className="hub-admin-modal-subtitle">Passwort eingeben um Einstellungen freizuschalten</div>
{adminError && <div className="hub-admin-modal-error">{adminError}</div>}
<input
className="hub-admin-modal-input"
type="password"
placeholder="Passwort"
value={adminPassword}
onChange={e => setAdminPassword(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleAdminLogin(); }}
autoFocus
/>
<button className="hub-admin-modal-login" onClick={handleAdminLogin}>
Login
</button>
</>
)}
</div>
</div>
)}
<main className="hub-content">
{plugins.length === 0 ? (
<div className="hub-empty">

View file

@ -472,24 +472,30 @@
/* ── Empty state ── */
.gl-empty {
text-align: center;
padding: 60px 20px;
flex: 1; display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 16px;
padding: 40px; height: 100%;
}
.gl-empty-icon {
font-size: 48px;
margin-bottom: 16px;
font-size: 64px; line-height: 1;
filter: drop-shadow(0 0 20px rgba(230,126,34,0.5));
animation: gl-empty-float 3s ease-in-out infinite;
}
@keyframes gl-empty-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
.gl-empty h3 {
color: var(--text-normal);
margin: 0 0 8px;
font-size: 26px; font-weight: 700; color: #f2f3f5;
letter-spacing: -0.5px; margin: 0;
}
.gl-empty p {
color: var(--text-faint);
margin: 0;
font-size: 14px;
font-size: 15px; color: #80848e;
text-align: center; max-width: 360px; line-height: 1.5; margin: 0;
}
/* ── Common game playtime chips ── */

View file

@ -456,22 +456,26 @@
}
.lol-empty {
text-align: center;
padding: 60px 20px;
color: var(--text-faint);
flex: 1; display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 16px;
padding: 40px; height: 100%;
}
.lol-empty-icon {
font-size: 48px;
margin-bottom: 12px;
font-size: 64px; line-height: 1;
filter: drop-shadow(0 0 20px rgba(230,126,34,0.5));
animation: lol-float 3s ease-in-out infinite;
}
@keyframes lol-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
.lol-empty h3 {
margin: 0 0 8px;
color: var(--text-muted);
font-size: 16px;
font-size: 26px; font-weight: 700; color: #f2f3f5;
letter-spacing: -0.5px; margin: 0;
}
.lol-empty p {
margin: 0;
font-size: 13px;
font-size: 15px; color: #80848e;
text-align: center; max-width: 360px; line-height: 1.5; margin: 0;
}
/* ── Load more ── */

View file

@ -3,7 +3,7 @@
/* Soundboard Plugin — ported from Jukebox styles */
/*
Theme Variables Default (Discord Blurple)
Theme Variables Default (Orange Accent)
*/
.sb-app {
--bg-deep: #1a1b1e;
@ -18,10 +18,10 @@
--text-muted: #949ba4;
--text-faint: #6d6f78;
--accent: #5865f2;
--accent-rgb: 88, 101, 242;
--accent-hover: #4752c4;
--accent-glow: rgba(88, 101, 242, .45);
--accent: #e67e22;
--accent-rgb: 230, 126, 34;
--accent-hover: #d35400;
--accent-glow: rgba(230, 126, 34, .45);
--green: #23a55a;
--red: #f23f42;
@ -91,6 +91,18 @@
--accent-glow: rgba(52, 152, 219, .4);
}
/* ── Theme: Cherry ── */
.sb-app[data-theme="cherry"] {
--bg-deep: #1a0f14;
--bg-primary: #221419;
--bg-secondary: #2f1c22;
--bg-tertiary: #3d242c;
--accent: #e74c3c;
--accent-rgb: 231, 76, 60;
--accent-hover: #c0392b;
--accent-glow: rgba(231, 76, 60, .4);
}
/*
App Layout
*/
@ -310,6 +322,33 @@
margin-left: 2px;
}
/* ── Disconnect Button ── */
.disconnect-btn {
display: flex;
align-items: center;
gap: 5px;
padding: 4px 10px;
border-radius: 20px;
background: rgba(242, 63, 66, .1);
border: 1px solid rgba(242, 63, 66, .2);
backdrop-filter: blur(8px);
color: var(--red);
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all var(--transition);
}
.disconnect-btn:hover {
background: rgba(242, 63, 66, .2);
border-color: rgba(242, 63, 66, .4);
box-shadow: 0 0 12px rgba(242, 63, 66, .15);
}
.disconnect-btn:active {
transform: scale(0.97);
}
/* ── Connection Details Modal ── */
.conn-modal-overlay {
position: fixed;
@ -340,9 +379,10 @@
align-items: center;
gap: 8px;
padding: 14px 16px;
border-bottom: 1px solid var(--border);
font-weight: 700;
border-bottom: 1px solid rgba(255,255,255,.06);
font-size: 14px;
font-weight: 700;
color: var(--text-normal);
}
.conn-modal-close {
margin-left: auto;
@ -438,7 +478,9 @@
gap: 6px;
padding: 6px 14px;
border-radius: 20px;
background: var(--bg-tertiary);
background: rgba(255, 255, 255, .05);
border: 1px solid rgba(255, 255, 255, .08);
backdrop-filter: blur(8px);
color: var(--text-muted);
font-family: var(--font);
font-size: 13px;
@ -449,13 +491,16 @@
}
.cat-tab:hover {
background: var(--bg-modifier-selected);
background: rgba(255, 255, 255, .1);
border-color: rgba(255, 255, 255, .15);
color: var(--text-normal);
}
.cat-tab.active {
background: var(--accent);
color: var(--white);
border-color: var(--accent);
box-shadow: 0 2px 12px rgba(var(--accent-rgb), .35);
}
.tab-count {
@ -489,9 +534,9 @@
width: 100%;
height: 32px;
padding: 0 28px 0 32px;
border: 1px solid rgba(255, 255, 255, .06);
border: 1px solid rgba(255, 255, 255, .08);
border-radius: 20px;
background: var(--bg-secondary);
background: var(--bg-deep);
color: var(--text-normal);
font-family: var(--font);
font-size: 13px;
@ -572,8 +617,8 @@
height: 24px;
padding: 0 10px;
border-radius: 14px;
border: 1px solid rgba(var(--accent-rgb, 88, 101, 242), .45);
background: rgba(var(--accent-rgb, 88, 101, 242), .12);
border: 1px solid rgba(var(--accent-rgb), .45);
background: rgba(var(--accent-rgb), .12);
color: var(--accent);
font-size: 11px;
font-weight: 700;
@ -639,7 +684,7 @@
}
.tb-btn.random {
border-color: rgba(88, 101, 242, .3);
border-color: rgba(230, 126, 34, .3);
color: var(--accent);
}
@ -834,7 +879,7 @@
gap: 4px;
padding: 3px 8px;
border-radius: 999px;
background: rgba(var(--accent-rgb, 88, 101, 242), .15);
background: rgba(var(--accent-rgb), .15);
color: var(--accent);
font-size: 11px;
font-weight: 600;
@ -875,8 +920,9 @@
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
background: var(--bg-secondary);
border: 1px solid rgba(255, 255, 255, .06);
background: rgba(255, 255, 255, .05);
border: 1px solid rgba(255, 255, 255, .08);
backdrop-filter: blur(8px);
white-space: nowrap;
cursor: pointer;
transition: all var(--transition);
@ -884,13 +930,16 @@
}
.cat-chip:hover {
border-color: rgba(255, 255, 255, .12);
border-color: rgba(255, 255, 255, .15);
color: var(--text-normal);
background: var(--bg-tertiary);
background: rgba(255, 255, 255, .1);
}
.cat-chip.active {
background: rgba(88, 101, 242, .1);
background: var(--accent);
color: var(--white);
border-color: var(--accent);
box-shadow: 0 2px 12px rgba(var(--accent-rgb), .35);
}
.cat-dot {
@ -924,7 +973,7 @@
}
/*
Sound Card
Sound Card Glass Morphism
*/
.sound-card {
position: relative;
@ -934,11 +983,13 @@
justify-content: center;
gap: 3px;
padding: 12px 6px 8px;
background: var(--bg-secondary);
background: rgba(255, 255, 255, .05);
border: 1px solid rgba(255, 255, 255, .08);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border-radius: var(--radius-lg);
cursor: pointer;
transition: all var(--transition);
border: 2px solid transparent;
user-select: none;
overflow: hidden;
aspect-ratio: 1;
@ -953,15 +1004,14 @@
border-radius: inherit;
opacity: 0;
transition: opacity var(--transition);
background: radial-gradient(ellipse at center, var(--accent-glow) 0%, transparent 70%);
background: radial-gradient(circle at 50% 0%, rgba(var(--accent-rgb), .12), transparent 70%);
pointer-events: none;
}
.sound-card:hover {
background: var(--bg-tertiary);
transform: translateY(-3px);
box-shadow: var(--shadow-med), 0 0 20px var(--accent-glow);
border-color: rgba(88, 101, 242, .2);
transform: scale(1.05);
border-color: rgba(var(--accent-rgb), .35);
box-shadow: 0 4px 20px rgba(var(--accent-rgb), .15);
}
.sound-card:hover::before {
@ -969,7 +1019,7 @@
}
.sound-card:active {
transform: translateY(0);
transform: scale(0.97);
transition-duration: 50ms;
}
@ -992,7 +1042,7 @@
.ripple {
position: absolute;
border-radius: 50%;
background: rgba(88, 101, 242, .3);
background: rgba(var(--accent-rgb), .3);
transform: scale(0);
animation: ripple-expand 500ms ease-out forwards;
pointer-events: none;
@ -1171,8 +1221,8 @@
gap: 6px;
padding: 4px 12px;
border-radius: 20px;
background: rgba(var(--accent-rgb, 88, 101, 242), .12);
border: 1px solid rgba(var(--accent-rgb, 88, 101, 242), .2);
background: rgba(var(--accent-rgb), .12);
border: 1px solid rgba(var(--accent-rgb), .2);
font-size: 12px;
color: var(--text-muted);
max-width: none;
@ -1251,23 +1301,26 @@
.vol-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 8px rgba(var(--accent-rgb), .5);
cursor: pointer;
transition: transform var(--transition);
transition: transform var(--transition), box-shadow var(--transition);
}
.vol-slider::-webkit-slider-thumb:hover {
transform: scale(1.3);
box-shadow: 0 0 14px rgba(var(--accent-rgb), .7);
}
.vol-slider::-moz-range-thumb {
width: 12px;
height: 12px;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 8px rgba(var(--accent-rgb), .5);
border: none;
cursor: pointer;
}
@ -2042,7 +2095,6 @@
z-index: 300;
animation: fade-in 150ms ease;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.dl-modal {
width: 420px; max-width: 92vw;

View file

@ -356,23 +356,26 @@
/* ── Empty state ── */
.stream-empty {
text-align: center;
padding: 60px 20px;
color: var(--text-muted);
flex: 1; display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 16px;
padding: 40px; height: 100%;
}
.stream-empty-icon {
font-size: 48px;
margin-bottom: 12px;
opacity: 0.4;
font-size: 64px; line-height: 1;
filter: drop-shadow(0 0 20px rgba(230,126,34,0.5));
animation: stream-float 3s ease-in-out infinite;
}
@keyframes stream-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
.stream-empty h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-normal);
margin-bottom: 6px;
font-size: 26px; font-weight: 700; color: #f2f3f5;
letter-spacing: -0.5px; margin: 0;
}
.stream-empty p {
font-size: 14px;
font-size: 15px; color: #80848e;
text-align: center; max-width: 360px; line-height: 1.5; margin: 0;
}
/* ── Error ── */

View file

@ -161,23 +161,26 @@
/* ── Empty state ── */
.wt-empty {
text-align: center;
padding: 60px 20px;
color: var(--text-muted);
flex: 1; display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 16px;
padding: 40px; height: 100%;
}
.wt-empty-icon {
font-size: 48px;
margin-bottom: 12px;
opacity: 0.4;
font-size: 64px; line-height: 1;
filter: drop-shadow(0 0 20px rgba(230,126,34,0.5));
animation: wt-float 3s ease-in-out infinite;
}
@keyframes wt-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
.wt-empty h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-normal);
margin-bottom: 6px;
font-size: 26px; font-weight: 700; color: #f2f3f5;
letter-spacing: -0.5px; margin: 0;
}
.wt-empty p {
font-size: 14px;
font-size: 15px; color: #80848e;
text-align: center; max-width: 360px; line-height: 1.5; margin: 0;
}
/* ── Error ── */

View file

@ -4,19 +4,27 @@
--bg-primary: #1e1f22;
--bg-secondary: #2b2d31;
--bg-tertiary: #313338;
--text-normal: #dbdee1;
--text-muted: #949ba4;
--text-faint: #6d6f78;
--bg-light: #3a3c41;
--bg-lighter: #43464d;
--text-normal: #f2f3f5;
--text-muted: #b5bac1;
--text-faint: #80848e;
--accent: #e67e22;
--accent-rgb: 230, 126, 34;
--accent-hover: #d35400;
--success: #57d28f;
--accent-glow: rgba(230, 126, 34, 0.5);
--accent-dim: rgba(230, 126, 34, 0.35);
--accent-subtle: rgba(230, 126, 34, 0.12);
--glass-bg: rgba(255, 255, 255, 0.05);
--glass-border: rgba(255, 255, 255, 0.08);
--glass-bg-hover: rgba(255, 255, 255, 0.09);
--success: #23a559;
--danger: #ed4245;
--warning: #fee75c;
--border: rgba(255, 255, 255, 0.06);
--radius: 8px;
--radius-lg: 12px;
--transition: 150ms ease;
--transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
--font: 'Segoe UI', system-ui, -apple-system, sans-serif;
--header-height: 56px;
}
@ -78,14 +86,18 @@ html, body {
.hub-logo {
font-size: 24px;
line-height: 1;
filter: drop-shadow(0 0 6px var(--accent-glow));
}
.hub-title {
font-size: 18px;
font-weight: 700;
color: var(--text-normal);
letter-spacing: -0.02em;
white-space: nowrap;
background: linear-gradient(135deg, var(--accent), #f39c12);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* ── Connection Status Dot ── */
@ -155,8 +167,8 @@ html, body {
}
.hub-tab.active {
color: var(--accent);
background: rgba(var(--accent-rgb), 0.1);
color: var(--text-normal);
background: var(--accent-subtle);
}
.hub-tab.active::after {
@ -165,10 +177,11 @@ html, body {
bottom: -1px;
left: 50%;
transform: translateX(-50%);
width: calc(100% - 16px);
width: 70%;
height: 2px;
background: var(--accent);
border-radius: 1px;
border-radius: 2px;
box-shadow: 0 0 8px var(--accent-glow), 0 0 16px rgba(230,126,34,0.2);
}
.hub-tab-icon {
@ -616,6 +629,213 @@ html, body {
color: var(--text-normal);
}
/* ── Admin Button ── */
.hub-admin-btn {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
color: var(--text-muted);
font-size: 16px;
width: 36px;
height: 36px;
border-radius: 50%;
cursor: pointer;
transition: all var(--transition);
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.hub-admin-btn:hover {
background: var(--glass-bg-hover);
border-color: var(--accent-dim);
box-shadow: 0 0 12px rgba(230,126,34,0.15);
}
.hub-admin-btn.logged-in {
border-color: var(--success);
}
.hub-admin-green-dot {
position: absolute;
top: 1px;
right: 1px;
width: 8px;
height: 8px;
background: var(--success);
border-radius: 50%;
border: 2px solid var(--bg-primary);
}
/* ── Avatar ── */
.hub-avatar {
width: 34px;
height: 34px;
border-radius: 50%;
background: linear-gradient(135deg, var(--accent), #f39c12);
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 700;
color: #fff;
box-shadow: 0 0 0 2px var(--bg-primary);
}
/* ── Admin Modal ── */
.hub-admin-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.7);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
animation: hub-modal-fade-in 0.2s ease;
}
@keyframes hub-modal-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.hub-admin-modal {
background: var(--bg-secondary);
border: 1px solid var(--glass-border);
border-radius: var(--radius-lg);
padding: 32px;
width: 360px;
max-width: 90vw;
box-shadow: 0 16px 48px rgba(0,0,0,0.5), 0 0 40px rgba(230,126,34,0.08);
animation: hub-modal-slide-in 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes hub-modal-slide-in {
from { opacity: 0; transform: scale(0.95) translateY(10px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.hub-admin-modal-title {
font-size: 20px;
font-weight: 700;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 8px;
}
.hub-admin-modal-subtitle {
font-size: 13px;
color: var(--text-faint);
margin-bottom: 24px;
}
.hub-admin-modal-error {
font-size: 13px;
color: var(--danger);
margin-bottom: 12px;
padding: 8px 12px;
background: rgba(237, 66, 69, 0.1);
border-radius: var(--radius);
}
.hub-admin-modal-input {
width: 100%;
background: var(--bg-deep);
border: 1px solid var(--glass-border);
border-radius: var(--radius);
color: var(--text-normal);
font-family: var(--font);
font-size: 14px;
padding: 10px 14px;
outline: none;
transition: border-color var(--transition), box-shadow var(--transition);
margin-bottom: 16px;
}
.hub-admin-modal-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(230,126,34,0.12);
}
.hub-admin-modal-login {
width: 100%;
background: var(--accent);
border: none;
border-radius: var(--radius);
color: #fff;
font-family: var(--font);
font-size: 14px;
font-weight: 600;
padding: 10px;
cursor: pointer;
transition: all var(--transition);
box-shadow: 0 2px 10px var(--accent-dim);
}
.hub-admin-modal-login:hover {
background: var(--accent-hover);
box-shadow: 0 4px 16px var(--accent-glow);
}
.hub-admin-modal-info {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.hub-admin-modal-avatar {
width: 44px;
height: 44px;
border-radius: 50%;
background: linear-gradient(135deg, var(--accent), #f39c12);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 700;
color: #fff;
box-shadow: 0 0 12px var(--accent-dim);
}
.hub-admin-modal-text {
display: flex;
flex-direction: column;
gap: 2px;
}
.hub-admin-modal-name {
font-weight: 600;
font-size: 15px;
}
.hub-admin-modal-role {
font-size: 12px;
color: var(--success);
font-weight: 500;
}
.hub-admin-modal-logout {
width: 100%;
background: rgba(237,66,69,0.12);
border: 1px solid rgba(237,66,69,0.25);
border-radius: var(--radius);
color: var(--danger);
font-family: var(--font);
font-size: 14px;
font-weight: 500;
padding: 10px;
cursor: pointer;
transition: all var(--transition);
}
.hub-admin-modal-logout:hover {
background: rgba(237,66,69,0.2);
}
/* ── Focus Styles ── */
:focus-visible {
outline: 2px solid var(--accent);