diff --git a/.forgejo/workflows/build-deploy.yml b/.forgejo/workflows/build-deploy.yml index 0fcf4c5..19322d6 100644 --- a/.forgejo/workflows/build-deploy.yml +++ b/.forgejo/workflows/build-deploy.yml @@ -6,6 +6,7 @@ on: env: REGISTRY: forgejo.adriahub.de + REGISTRY_MIRROR: forgejo.daddelolymp.de IMAGE: root/gaming-hub jobs: @@ -66,13 +67,27 @@ jobs: docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest fi + - name: Mirror to registry (daddelolymp) + run: | + echo "${{ secrets.REGISTRY_DADDELOLYMP_PASSWORD }}" | docker login ${{ env.REGISTRY_MIRROR }} -u root --password-stdin + docker tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ steps.vars.outputs.tag }} \ + ${{ env.REGISTRY_MIRROR }}/${{ env.IMAGE }}:${{ steps.vars.outputs.tag }} + docker push ${{ env.REGISTRY_MIRROR }}/${{ env.IMAGE }}:${{ steps.vars.outputs.tag }} + if [ "${GITHUB_REF_NAME}" = "main" ]; then + docker tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest \ + ${{ env.REGISTRY_MIRROR }}/${{ env.IMAGE }}:latest + docker push ${{ env.REGISTRY_MIRROR }}/${{ env.IMAGE }}:latest + fi + - name: Cleanup build artifacts if: always() run: | TAG="${{ steps.vars.outputs.tag }}" docker rmi "${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG}" 2>/dev/null || true + docker rmi "${{ env.REGISTRY_MIRROR }}/${{ env.IMAGE }}:${TAG}" 2>/dev/null || true if [ "${GITHUB_REF_NAME}" = "main" ]; then docker rmi "${{ env.REGISTRY }}/${{ env.IMAGE }}:latest" 2>/dev/null || true + docker rmi "${{ env.REGISTRY_MIRROR }}/${{ env.IMAGE }}:latest" 2>/dev/null || true fi docker image prune -f 2>/dev/null || true docker builder prune -f --keep-storage=4GB 2>/dev/null || true @@ -117,6 +132,7 @@ jobs: -e DISCORD_CLIENT_ID="${{ secrets.DISCORD_CLIENT_ID }}" \ -e DISCORD_CLIENT_SECRET="${{ secrets.DISCORD_CLIENT_SECRET }}" \ -v /mnt/cache/appdata/gaming-hub/data:/data:rw \ + -v /mnt/cache/appdata/dockge/container/jukebox/sounds/:/data/sounds:rw \ "$DEPLOY_IMAGE" docker ps --filter name="$CONTAINER_NAME" diff --git a/VERSION b/VERSION index fee0a27..f8e233b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.7 +1.9.0 diff --git a/web/src/App.tsx b/web/src/App.tsx index a51e0b5..2347765 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -48,14 +48,6 @@ export function registerTab(pluginName: string, component: React.FC<{ data: any; type UpdateStatus = 'idle' | 'checking' | 'available' | 'downloading' | 'ready' | 'upToDate' | 'error'; -const APP_THEMES = [ - { id: 'default', color: '#e67e22', label: 'Ember' }, - { id: 'purple', color: '#9b59b6', label: 'Amethyst' }, - { id: 'forest', color: '#2ecc71', label: 'Jade' }, - { id: 'ocean', color: '#3498db', label: 'Ocean' }, - { id: 'cherry', color: '#e74c6f', label: 'Rose' }, -]; - export default function App() { const [connected, setConnected] = useState(false); const [plugins, setPlugins] = useState([]); @@ -66,31 +58,8 @@ export default function App() { localStorage.setItem('hub_activeTab', tab); }; const [showVersionModal, setShowVersionModal] = useState(false); - const [showSettingsModal, setShowSettingsModal] = useState(false); const [pluginData, setPluginData] = useState>({}); - // ── Global Theme ── - const [theme, setThemeRaw] = useState(() => localStorage.getItem('hub-theme') || 'default'); - const setTheme = (t: string) => { - setThemeRaw(t); - localStorage.setItem('hub-theme', t); - }; - - // ── Clock ── - const [clock, setClock] = useState(''); - useEffect(() => { - const update = () => { - const now = new Date(); - const h = String(now.getHours()).padStart(2, '0'); - const m = String(now.getMinutes()).padStart(2, '0'); - const s = String(now.getSeconds()).padStart(2, '0'); - setClock(`${h}:${m}:${s}`); - }; - update(); - const id = setInterval(update, 1000); - return () => clearInterval(id); - }, []); - // ── Unified Auth State ── const [user, setUser] = useState({ authenticated: false }); const [providers, setProviders] = useState({ discord: false, steam: false, admin: false }); @@ -279,7 +248,7 @@ export default function App() { } return ( -
+
{'\u{1F3AE}'} @@ -341,31 +310,13 @@ export default function App() { )} - - - {clock.slice(0, 5)}{clock.slice(5)} - { @@ -387,37 +338,6 @@ export default function App() {
- {/* Settings Modal */} - {showSettingsModal && ( -
setShowSettingsModal(false)}> -
e.stopPropagation()}> -
- Einstellungen - -
-
-
- Farbschema -
- {APP_THEMES.map(t => ( - - ))} -
-
-
-
-
- )} - {showVersionModal && (
setShowVersionModal(false)}>
e.stopPropagation()}> diff --git a/web/src/plugins/radio/RadioTab.tsx b/web/src/plugins/radio/RadioTab.tsx index 629d08a..60b9c89 100644 --- a/web/src/plugins/radio/RadioTab.tsx +++ b/web/src/plugins/radio/RadioTab.tsx @@ -54,6 +54,13 @@ interface VoiceStats { connectedSince: string | null; } +const THEMES = [ + { id: 'default', color: '#e67e22', label: 'Sunset' }, + { id: 'purple', color: '#9b59b6', label: 'Midnight' }, + { id: 'forest', color: '#2ecc71', label: 'Forest' }, + { id: 'ocean', color: '#3498db', label: 'Ocean' }, + { id: 'cherry', color: '#e74c6f', label: 'Cherry' }, +]; // ── Zoom scaling constants ── const BASE_ALT = 2.0; @@ -64,6 +71,7 @@ export default function RadioTab({ data }: { data: any }) { const globeRef = useRef(null); const rotationResumeRef = useRef>(undefined); + const [theme, setTheme] = useState(() => localStorage.getItem('radio-theme') || 'default'); const [places, setPlaces] = useState([]); const [selectedPlace, setSelectedPlace] = useState(null); const [stations, setStations] = useState([]); @@ -144,24 +152,17 @@ export default function RadioTab({ data }: { data: any }) { } }, [data, selectedGuild]); - // ── Update globe colors when global theme changes ── + // ── Theme persist + update globe colors ── useEffect(() => { - function updateGlobeColors() { - if (!globeRef.current || !containerRef.current) return; - const style = getComputedStyle(containerRef.current); + localStorage.setItem('radio-theme', theme); + if (globeRef.current && containerRef.current) { + const style = getComputedStyle(containerRef.current.parentElement!); const accentRgb = style.getPropertyValue('--accent-rgb').trim(); - if (!accentRgb) return; globeRef.current .pointColor(() => `rgba(${accentRgb}, 0.85)`) .atmosphereColor(`rgba(${accentRgb}, 0.25)`); } - updateGlobeColors(); - const app = document.querySelector('.hub-app'); - if (!app) return; - const observer = new MutationObserver(updateGlobeColors); - observer.observe(app, { attributes: true, attributeFilter: ['data-theme'] }); - return () => observer.disconnect(); - }, []); + }, [theme]); // ── Helper: pause globe rotation for 5s (only resumes if no panel open) ── const selectedPlaceRef = useRef(selectedPlace); @@ -465,7 +466,7 @@ export default function RadioTab({ data }: { data: any }) { const currentGuild = guilds.find(g => g.id === selectedGuild); return ( -
+
{/* ═══ TOPBAR ═══ */}
@@ -525,6 +526,17 @@ export default function RadioTab({ data }: { data: any }) { )} +
+ {THEMES.map(t => ( +
setTheme(t.id)} + /> + ))} +
diff --git a/web/src/plugins/soundboard/SoundboardTab.tsx b/web/src/plugins/soundboard/SoundboardTab.tsx index 213ff84..a58c165 100644 --- a/web/src/plugins/soundboard/SoundboardTab.tsx +++ b/web/src/plugins/soundboard/SoundboardTab.tsx @@ -266,6 +266,13 @@ function apiUploadFileWithName( CONSTANTS ══════════════════════════════════════════════════════════════════ */ +const THEMES = [ + { id: 'default', color: '#5865f2', label: 'Discord' }, + { id: 'purple', color: '#9b59b6', label: 'Midnight' }, + { id: 'forest', color: '#2ecc71', label: 'Forest' }, + { id: 'sunset', color: '#e67e22', label: 'Sunset' }, + { id: 'ocean', color: '#3498db', label: 'Ocean' }, +]; const CAT_PALETTE = [ '#3b82f6', '#f59e0b', '#8b5cf6', '#ec4899', '#14b8a6', @@ -343,6 +350,8 @@ export default function SoundboardTab({ data, isAdmin: isAdminProp }: Soundboard /* ── Preferences ── */ const [favs, setFavs] = useState>({}); + const [theme, setTheme] = useState(() => localStorage.getItem('jb-theme') || 'default'); + const [cardSize, setCardSize] = useState(() => parseInt(localStorage.getItem('jb-card-size') || '110')); /* ── Party ── */ const [chaosMode, setChaosMode] = useState(false); @@ -373,6 +382,7 @@ export default function SoundboardTab({ data, isAdmin: isAdminProp }: Soundboard /* ── UI ── */ const [notification, setNotification] = useState<{ msg: string; type: 'info' | 'error' } | null>(null); + const [clock, setClock] = useState(''); const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number; sound: Sound } | null>(null); const [refreshKey, setRefreshKey] = useState(0); @@ -458,6 +468,20 @@ export default function SoundboardTab({ data, isAdmin: isAdminProp }: Soundboard channels.find(c => `${c.guildId}:${c.channelId}` === selected), [channels, selected]); + /* ── Clock ── */ + useEffect(() => { + const update = () => { + const now = new Date(); + const h = String(now.getHours()).padStart(2, '0'); + const m = String(now.getMinutes()).padStart(2, '0'); + const s = String(now.getSeconds()).padStart(2, '0'); + setClock(`${h}:${m}:${s}`); + }; + update(); + const id = setInterval(update, 1000); + return () => clearInterval(id); + }, []); + /* ── Init ── */ useEffect(() => { (async () => { @@ -476,6 +500,23 @@ export default function SoundboardTab({ data, isAdmin: isAdminProp }: Soundboard // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + /* ── Theme (persist only, data-theme is set on .sb-app div) ── */ + useEffect(() => { + localStorage.setItem('jb-theme', theme); + }, [theme]); + + /* ── Card size (scoped to .sb-app container) ── */ + const sbAppRef = useRef(null); + useEffect(() => { + const el = sbAppRef.current; + if (!el) return; + el.style.setProperty('--card-size', cardSize + 'px'); + const ratio = cardSize / 110; + el.style.setProperty('--card-emoji', Math.round(28 * ratio) + 'px'); + el.style.setProperty('--card-font', Math.max(9, Math.round(11 * ratio)) + 'px'); + localStorage.setItem('jb-card-size', String(cardSize)); + }, [cardSize]); + /* ── SSE via props.data instead of own EventSource ── */ useEffect(() => { if (!data) return; @@ -786,11 +827,14 @@ export default function SoundboardTab({ data, isAdmin: isAdminProp }: Soundboard const analyticsTop = analytics.mostPlayed.slice(0, 10); const totalSoundsDisplay = analytics.totalSounds || total; + const clockMain = clock.slice(0, 5); + const clockSec = clock.slice(5); + /* ════════════════════════════════════════════ RENDER ════════════════════════════════════════════ */ return ( -
+
{chaosMode &&
} {/* ═══ TOPBAR ═══ */} @@ -839,6 +883,10 @@ export default function SoundboardTab({ data, isAdmin: isAdminProp }: Soundboard
+
+
{clockMain}{clockSec}
+
+
{lastPlayed && (
@@ -849,6 +897,15 @@ export default function SoundboardTab({ data, isAdmin: isAdminProp }: Soundboard Last Played: {lastPlayed}
)} + {selected && ( +
setShowConnModal(true)} style={{cursor:'pointer'}} title="Verbindungsdetails"> + + Verbunden + {voiceStats?.voicePing != null && ( + {voiceStats.voicePing}ms + )} +
+ )}
@@ -979,16 +1036,29 @@ export default function SoundboardTab({ data, isAdmin: isAdminProp }: Soundboard Stop - {selected && ( -
setShowConnModal(true)} style={{cursor:'pointer'}} title="Verbindungsdetails"> - - Verbunden - {voiceStats?.voicePing != null && ( - {voiceStats.voicePing}ms - )} -
- )} +
+ grid_view + setCardSize(parseInt(e.target.value))} + /> +
+
+ {THEMES.map(t => ( +
setTheme(t.id)} + /> + ))} +
diff --git a/web/src/plugins/soundboard/soundboard.css b/web/src/plugins/soundboard/soundboard.css index ff87762..4abee5f 100644 --- a/web/src/plugins/soundboard/soundboard.css +++ b/web/src/plugins/soundboard/soundboard.css @@ -6,20 +6,35 @@ Theme Variables — Default (Discord Blurple) ──────────────────────────────────────────── */ .sb-app { - /* Inherits all theme vars (--bg-*, --accent-*, --text-*) from .hub-app */ - --accent-glow: rgba(var(--accent-rgb), 0.45); + --bg-deep: #1a1810; + --bg-primary: #211e17; + --bg-secondary: #2a2620; + --bg-tertiary: #322d26; --bg-modifier-hover: rgba(79, 84, 92, .16); --bg-modifier-active: rgba(79, 84, 92, .24); --bg-modifier-selected: rgba(79, 84, 92, .32); + --text-normal: #dbdee1; + --text-muted: #949ba4; + --text-faint: #6d6f78; + + --accent: #5865f2; + --accent-rgb: 88, 101, 242; + --accent-hover: #4752c4; + --accent-glow: rgba(88, 101, 242, .45); + --green: #23a55a; --red: #f23f42; --yellow: #f0b232; --white: #ffffff; + --font: 'DM Sans', 'Outfit', 'gg sans', 'Noto Sans', Whitney, 'Helvetica Neue', Helvetica, Arial, sans-serif; + --radius: 8px; + --radius-lg: 12px; --shadow-low: 0 1px 3px rgba(0, 0, 0, .24); --shadow-med: 0 4px 12px rgba(0, 0, 0, .32); --shadow-high: 0 8px 24px rgba(0, 0, 0, .4); + --transition: 150ms cubic-bezier(.4, 0, .2, 1); --card-size: 110px; --card-emoji: 28px; @@ -28,6 +43,53 @@ color-scheme: dark; } +/* ── Theme: Midnight Purple ── */ +.sb-app[data-theme="purple"] { + --bg-deep: #13111c; + --bg-primary: #1a1726; + --bg-secondary: #241f35; + --bg-tertiary: #2e2845; + --accent: #9b59b6; + --accent-rgb: 155, 89, 182; + --accent-hover: #8e44ad; + --accent-glow: rgba(155, 89, 182, .45); +} + +/* ── Theme: Forest ── */ +.sb-app[data-theme="forest"] { + --bg-deep: #0f1a14; + --bg-primary: #142119; + --bg-secondary: #1c2e22; + --bg-tertiary: #253a2c; + --accent: #2ecc71; + --accent-rgb: 46, 204, 113; + --accent-hover: #27ae60; + --accent-glow: rgba(46, 204, 113, .4); +} + +/* ── Theme: Sunset ── */ +.sb-app[data-theme="sunset"] { + --bg-deep: #1a1210; + --bg-primary: #231815; + --bg-secondary: #2f201c; + --bg-tertiary: #3d2a24; + --accent: #e67e22; + --accent-rgb: 230, 126, 34; + --accent-hover: #d35400; + --accent-glow: rgba(230, 126, 34, .4); +} + +/* ── Theme: Ocean ── */ +.sb-app[data-theme="ocean"] { + --bg-deep: #0a1628; + --bg-primary: #0f1e33; + --bg-secondary: #162a42; + --bg-tertiary: #1e3652; + --accent: #3498db; + --accent-rgb: 52, 152, 219; + --accent-hover: #2980b9; + --accent-glow: rgba(52, 152, 219, .4); +} /* ──────────────────────────────────────────── App Layout @@ -80,6 +142,28 @@ letter-spacing: -.02em; } +/* ── Clock ── */ +.clock-wrap { + flex: 1; + display: flex; + justify-content: center; +} + +.clock { + font-size: 22px; + font-weight: 700; + color: var(--text-normal); + letter-spacing: .02em; + font-variant-numeric: tabular-nums; + opacity: .9; +} + +.clock-seconds { + font-size: 14px; + color: var(--text-faint); + font-weight: 500; +} + .topbar-right { display: flex; align-items: center; @@ -598,6 +682,56 @@ border-color: var(--red); } +/* ── Size Slider ── */ +.size-control { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 4px; + background: var(--bg-tertiary); + border: 1px solid rgba(255, 255, 255, .06); +} + +.size-control .sc-icon { + font-size: 14px; + color: var(--text-faint); +} + +.size-slider { + -webkit-appearance: none; + appearance: none; + width: 70px; + height: 3px; + border-radius: 2px; + background: var(--bg-modifier-selected); + outline: none; + cursor: pointer; +} + +.size-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--accent); + cursor: pointer; + transition: transform var(--transition); +} + +.size-slider::-webkit-slider-thumb:hover { + transform: scale(1.3); +} + +.size-slider::-moz-range-thumb { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--accent); + border: none; + cursor: pointer; +} + /* ── Theme Selector ── */ .theme-selector { display: flex; @@ -1576,6 +1710,7 @@ order: -1; } + .size-control, .theme-selector { display: none; } @@ -1595,6 +1730,14 @@ text-overflow: ellipsis; } + .clock { + font-size: 16px; + } + + .clock-seconds { + font-size: 11px; + } + .tb-btn span:not(.tb-icon) { display: none; } diff --git a/web/src/styles.css b/web/src/styles.css index 9a38deb..4d5c046 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -32,71 +32,6 @@ --header-height: 44px; } -/* ── Global App Themes ── */ -.hub-app[data-theme="purple"] { - --bg-deep: #13111c; - --bg-primary: #1a1726; - --bg-secondary: #241f35; - --bg-tertiary: #2e2845; - --bg-card: #241f35; - --bg-card-hover: #2e2845; - --bg-input: #110f19; - --bg-header: #161320; - --accent: #9b59b6; - --accent-rgb: 155, 89, 182; - --accent-hover: #8e44ad; - --accent-dim: rgba(155, 89, 182, 0.15); - --accent-border: rgba(155, 89, 182, 0.35); -} - -.hub-app[data-theme="forest"] { - --bg-deep: #0f1a14; - --bg-primary: #142119; - --bg-secondary: #1c2e22; - --bg-tertiary: #253a2c; - --bg-card: #1c2e22; - --bg-card-hover: #253a2c; - --bg-input: #0c150f; - --bg-header: #111c16; - --accent: #2ecc71; - --accent-rgb: 46, 204, 113; - --accent-hover: #27ae60; - --accent-dim: rgba(46, 204, 113, 0.15); - --accent-border: rgba(46, 204, 113, 0.35); -} - -.hub-app[data-theme="ocean"] { - --bg-deep: #0a1628; - --bg-primary: #0f1e33; - --bg-secondary: #162a42; - --bg-tertiary: #1e3652; - --bg-card: #162a42; - --bg-card-hover: #1e3652; - --bg-input: #081220; - --bg-header: #0c1a2e; - --accent: #3498db; - --accent-rgb: 52, 152, 219; - --accent-hover: #2980b9; - --accent-dim: rgba(52, 152, 219, 0.15); - --accent-border: rgba(52, 152, 219, 0.35); -} - -.hub-app[data-theme="cherry"] { - --bg-deep: #1a0f14; - --bg-primary: #22141a; - --bg-secondary: #301c25; - --bg-tertiary: #3e2530; - --bg-card: #301c25; - --bg-card-hover: #3e2530; - --bg-input: #150c10; - --bg-header: #1d1117; - --accent: #e74c6f; - --accent-rgb: 231, 76, 111; - --accent-hover: #c0392b; - --accent-dim: rgba(231, 76, 111, 0.15); - --accent-border: rgba(231, 76, 111, 0.35); -} - /* ── Reset & Base ── */ *, *::before, @@ -292,20 +227,6 @@ html, body { line-height: 1; } -/* ── Clock ── */ -.hub-clock { - font-size: 13px; - font-weight: 600; - color: var(--text-muted); - font-variant-numeric: tabular-nums; - letter-spacing: .02em; -} -.hub-clock-sec { - font-size: 10px; - color: var(--text-faint); - font-weight: 400; -} - .hub-version { font-size: 12px; color: var(--text-faint); @@ -426,130 +347,21 @@ html, body { max-width: 300px; } -/* ── Settings Button (Icon-Button per Styleguide §7) ── */ -.hub-settings-btn { - padding: 6px; - background: none; - color: var(--text-muted); - border: none; - border-radius: var(--radius); - cursor: pointer; - transition: all var(--transition); - display: flex; - align-items: center; - justify-content: center; -} -.hub-settings-btn:hover { - background: var(--bg-tertiary); - color: var(--text-normal); -} - -/* ── Settings Modal ── */ -.hub-settings-overlay { - position: fixed; - inset: 0; - z-index: 1000; - display: flex; - align-items: center; - justify-content: center; - background: rgba(0, 0, 0, 0.7); - backdrop-filter: blur(4px); -} -.hub-settings-modal { - background: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - width: 340px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); -} -.hub-settings-modal-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 20px 12px; - font-size: 14px; - font-weight: 600; - color: var(--text-normal); - border-bottom: 1px solid var(--border); -} -.hub-settings-modal-close { - background: none; - border: none; - color: var(--text-muted); - cursor: pointer; - font-size: 14px; - padding: 4px; - border-radius: var(--radius); - transition: all var(--transition); -} -.hub-settings-modal-close:hover { - background: var(--bg-tertiary); - color: var(--text-normal); -} -.hub-settings-modal-body { - padding: 16px 20px 20px; -} -.hub-settings-section-label { - display: block; - font-size: 11px; - font-weight: 500; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 10px; -} -.hub-settings-theme-grid { - display: flex; - flex-wrap: wrap; - gap: 8px; -} -.hub-settings-theme-btn { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - background: transparent; - border: 1px solid var(--border-strong); - border-radius: var(--radius); - color: var(--text-muted); - font-family: var(--font); - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: all var(--transition); -} -.hub-settings-theme-btn:hover { - color: var(--text-normal); - border-color: rgba(255, 255, 255, 0.15); -} -.hub-settings-theme-btn.active { - border-color: var(--accent); - color: var(--text-normal); - background: var(--accent-dim); -} -.hub-settings-theme-dot { - width: 12px; - height: 12px; - border-radius: 50%; - flex-shrink: 0; -} - -/* ── Refresh Button (Icon-Button per Styleguide §7) ── */ +/* ── Refresh Button ── */ .hub-refresh-btn { - padding: 6px; background: none; - color: var(--text-muted); border: none; - border-radius: var(--radius); + color: var(--text-muted); + font-size: 1rem; cursor: pointer; + padding: 4px 6px; + border-radius: var(--radius); transition: all var(--transition); - display: flex; - align-items: center; - justify-content: center; + line-height: 1; } .hub-refresh-btn:hover { - background: var(--bg-tertiary); - color: var(--text-normal); + color: var(--accent); + background: rgba(230, 126, 34, 0.1); } /* ── Admin Button (header) ── */ @@ -995,6 +807,60 @@ html, body { height: 100%; overflow: hidden; background: var(--bg-deep); + + /* Default-Theme Vars (scoped, damit data-theme sie überschreiben kann) */ + --bg-deep: #1a1810; + --bg-primary: #211e17; + --bg-secondary: #2a2620; + --bg-tertiary: #322d26; + --text-normal: #dbdee1; + --text-muted: #949ba4; + --text-faint: #6d6f78; + --accent: #e67e22; + --accent-rgb: 230, 126, 34; + --accent-hover: #d35400; + --border: rgba(255, 255, 255, 0.06); +} + +/* ── Radio Themes ── */ +.radio-container[data-theme="purple"] { + --bg-deep: #13111c; + --bg-primary: #1a1726; + --bg-secondary: #241f35; + --bg-tertiary: #2e2845; + --accent: #9b59b6; + --accent-rgb: 155, 89, 182; + --accent-hover: #8e44ad; +} + +.radio-container[data-theme="forest"] { + --bg-deep: #0f1a14; + --bg-primary: #142119; + --bg-secondary: #1c2e22; + --bg-tertiary: #253a2c; + --accent: #2ecc71; + --accent-rgb: 46, 204, 113; + --accent-hover: #27ae60; +} + +.radio-container[data-theme="ocean"] { + --bg-deep: #0a1628; + --bg-primary: #0f1e33; + --bg-secondary: #162a42; + --bg-tertiary: #1e3652; + --accent: #3498db; + --accent-rgb: 52, 152, 219; + --accent-hover: #2980b9; +} + +.radio-container[data-theme="cherry"] { + --bg-deep: #1a0f14; + --bg-primary: #22141a; + --bg-secondary: #301c25; + --bg-tertiary: #3e2530; + --accent: #e74c6f; + --accent-rgb: 231, 76, 111; + --accent-hover: #c0392b; } /* ── Globe ── */