2025-08-12 01:05:59 +02:00
|
|
|
<template>
|
|
|
|
|
<div class="settings-dialog">
|
|
|
|
|
<form @submit.prevent="handleSave" class="settings-form">
|
|
|
|
|
<div class="setting-group">
|
|
|
|
|
<h3>Audio Settings</h3>
|
|
|
|
|
|
|
|
|
|
<label class="setting-item">
|
|
|
|
|
<input
|
2025-08-22 19:01:45 +02:00
|
|
|
ref="soundInput"
|
2025-08-12 01:05:59 +02:00
|
|
|
type="checkbox"
|
|
|
|
|
v-model="localSettings.soundEnabled"
|
|
|
|
|
class="checkbox"
|
|
|
|
|
/>
|
|
|
|
|
<span>Enable sound effects</span>
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<label class="setting-item">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
v-model="localSettings.speechEnabled"
|
|
|
|
|
class="checkbox"
|
|
|
|
|
/>
|
|
|
|
|
<span>Enable speech synthesis (deprecated)</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="setting-group">
|
|
|
|
|
<h3>Text-to-Speech</h3>
|
|
|
|
|
|
|
|
|
|
<label class="setting-item">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
v-model="localSettings.ttsEnabled"
|
|
|
|
|
class="checkbox"
|
|
|
|
|
/>
|
|
|
|
|
<span>Enable text-to-speech announcements</span>
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<div class="setting-item" v-if="localSettings.ttsEnabled">
|
|
|
|
|
<label for="voice-select">Voice</label>
|
|
|
|
|
<select
|
|
|
|
|
id="voice-select"
|
|
|
|
|
v-model="selectedVoiceURI"
|
|
|
|
|
class="select"
|
|
|
|
|
@change="handleVoiceChange"
|
|
|
|
|
>
|
|
|
|
|
<option value="" disabled>Select a voice...</option>
|
|
|
|
|
<option
|
|
|
|
|
v-for="voice in availableVoices"
|
|
|
|
|
:key="voice.voiceURI"
|
|
|
|
|
:value="voice.voiceURI"
|
|
|
|
|
>
|
|
|
|
|
{{ voice.name }} ({{ voice.lang }})
|
|
|
|
|
</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="setting-item" v-if="localSettings.ttsEnabled">
|
|
|
|
|
<label for="rate-slider">Speech Rate: {{ localSettings.ttsRate.toFixed(1) }}</label>
|
|
|
|
|
<input
|
|
|
|
|
id="rate-slider"
|
|
|
|
|
type="range"
|
|
|
|
|
min="0.5"
|
|
|
|
|
max="2"
|
|
|
|
|
step="0.1"
|
|
|
|
|
v-model.number="localSettings.ttsRate"
|
|
|
|
|
class="slider"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="setting-item" v-if="localSettings.ttsEnabled">
|
|
|
|
|
<label for="pitch-slider">Speech Pitch: {{ localSettings.ttsPitch.toFixed(1) }}</label>
|
|
|
|
|
<input
|
|
|
|
|
id="pitch-slider"
|
|
|
|
|
type="range"
|
|
|
|
|
min="0"
|
|
|
|
|
max="2"
|
|
|
|
|
step="0.1"
|
|
|
|
|
v-model.number="localSettings.ttsPitch"
|
|
|
|
|
class="slider"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="setting-item" v-if="localSettings.ttsEnabled">
|
|
|
|
|
<label for="volume-slider">Speech Volume: {{ localSettings.ttsVolume.toFixed(1) }}</label>
|
|
|
|
|
<input
|
|
|
|
|
id="volume-slider"
|
|
|
|
|
type="range"
|
|
|
|
|
min="0"
|
|
|
|
|
max="1"
|
|
|
|
|
step="0.1"
|
|
|
|
|
v-model.number="localSettings.ttsVolume"
|
|
|
|
|
class="slider"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="setting-item" v-if="localSettings.ttsEnabled">
|
|
|
|
|
<BaseButton
|
|
|
|
|
type="button"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
size="sm"
|
|
|
|
|
@click="testSpeech"
|
|
|
|
|
:disabled="!selectedVoiceURI"
|
|
|
|
|
>
|
|
|
|
|
Test Speech
|
|
|
|
|
</BaseButton>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="setting-group">
|
|
|
|
|
<h3>Appearance</h3>
|
|
|
|
|
|
|
|
|
|
<div class="setting-item">
|
|
|
|
|
<label for="theme-select">Theme</label>
|
|
|
|
|
<select
|
|
|
|
|
id="theme-select"
|
|
|
|
|
v-model="localSettings.theme"
|
|
|
|
|
class="select"
|
|
|
|
|
>
|
|
|
|
|
<option value="auto">Auto (System)</option>
|
|
|
|
|
<option value="light">Light</option>
|
|
|
|
|
<option value="dark">Dark</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="setting-group" v-if="appStore.channels.length > 0">
|
|
|
|
|
<h3>Default Channel</h3>
|
|
|
|
|
|
|
|
|
|
<div class="setting-item">
|
|
|
|
|
<label for="default-channel-select">Default Channel</label>
|
|
|
|
|
<select
|
|
|
|
|
id="default-channel-select"
|
|
|
|
|
v-model="localSettings.defaultChannelId"
|
|
|
|
|
class="select"
|
|
|
|
|
>
|
|
|
|
|
<option :value="null">None</option>
|
|
|
|
|
<option
|
|
|
|
|
v-for="channel in appStore.channels"
|
|
|
|
|
:key="channel.id"
|
|
|
|
|
:value="channel.id"
|
|
|
|
|
>
|
|
|
|
|
{{ channel.name }}
|
|
|
|
|
</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-08-20 22:50:38 +02:00
|
|
|
<div class="setting-group">
|
|
|
|
|
<h3>Account</h3>
|
|
|
|
|
|
|
|
|
|
<div class="setting-item">
|
|
|
|
|
<label>Current Server</label>
|
|
|
|
|
<div class="server-info">
|
|
|
|
|
{{ currentServerUrl || 'Default' }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="setting-actions">
|
|
|
|
|
<BaseButton
|
|
|
|
|
type="button"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
@click="handleLogout"
|
|
|
|
|
:disabled="isSaving"
|
|
|
|
|
>
|
|
|
|
|
Logout
|
|
|
|
|
</BaseButton>
|
|
|
|
|
|
|
|
|
|
<BaseButton
|
|
|
|
|
type="button"
|
|
|
|
|
variant="danger"
|
|
|
|
|
@click="showResetConfirm = true"
|
|
|
|
|
:disabled="isSaving"
|
|
|
|
|
>
|
|
|
|
|
Reset All Data
|
|
|
|
|
</BaseButton>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-08-12 01:05:59 +02:00
|
|
|
<div class="form-actions">
|
|
|
|
|
<BaseButton
|
|
|
|
|
type="button"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
@click="$emit('close')"
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</BaseButton>
|
|
|
|
|
<BaseButton
|
|
|
|
|
type="submit"
|
|
|
|
|
:loading="isSaving"
|
|
|
|
|
>
|
|
|
|
|
Save Settings
|
|
|
|
|
</BaseButton>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
2025-08-20 22:50:38 +02:00
|
|
|
|
|
|
|
|
<!-- Reset Data Confirmation Dialog -->
|
|
|
|
|
<div v-if="showResetConfirm" class="confirm-overlay">
|
|
|
|
|
<div class="confirm-dialog">
|
|
|
|
|
<h3>Reset All Data</h3>
|
|
|
|
|
<p>This will permanently delete all local data including messages, settings, and authentication. This cannot be undone.</p>
|
|
|
|
|
<div class="confirm-actions">
|
|
|
|
|
<BaseButton
|
|
|
|
|
type="button"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
@click="showResetConfirm = false"
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</BaseButton>
|
|
|
|
|
<BaseButton
|
|
|
|
|
type="button"
|
|
|
|
|
variant="danger"
|
|
|
|
|
@click="handleResetData"
|
|
|
|
|
:loading="isResetting"
|
|
|
|
|
>
|
|
|
|
|
Reset All Data
|
|
|
|
|
</BaseButton>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-12 01:05:59 +02:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-08-20 22:50:38 +02:00
|
|
|
import { ref, reactive, onMounted, computed } from 'vue'
|
|
|
|
|
import { useRouter } from 'vue-router'
|
2025-08-12 01:05:59 +02:00
|
|
|
import { useAppStore } from '@/stores/app'
|
2025-08-20 22:50:38 +02:00
|
|
|
import { useAuthStore } from '@/stores/auth'
|
2025-08-12 01:05:59 +02:00
|
|
|
import { useToastStore } from '@/stores/toast'
|
|
|
|
|
import { useAudio } from '@/composables/useAudio'
|
2025-08-20 22:50:38 +02:00
|
|
|
import { clear } from 'idb-keyval'
|
2025-08-12 01:05:59 +02:00
|
|
|
import BaseButton from '@/components/base/BaseButton.vue'
|
|
|
|
|
import type { AppSettings } from '@/types'
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
close: []
|
|
|
|
|
}>()
|
|
|
|
|
|
2025-08-20 22:50:38 +02:00
|
|
|
const router = useRouter()
|
2025-08-12 01:05:59 +02:00
|
|
|
const appStore = useAppStore()
|
2025-08-20 22:50:38 +02:00
|
|
|
const authStore = useAuthStore()
|
2025-08-12 01:05:59 +02:00
|
|
|
const toastStore = useToastStore()
|
|
|
|
|
const { availableVoices, speak, setVoice } = useAudio()
|
|
|
|
|
|
|
|
|
|
const isSaving = ref(false)
|
2025-08-20 22:50:38 +02:00
|
|
|
const isResetting = ref(false)
|
|
|
|
|
const showResetConfirm = ref(false)
|
2025-08-12 01:05:59 +02:00
|
|
|
const selectedVoiceURI = ref('')
|
2025-08-22 19:01:45 +02:00
|
|
|
const soundInput = ref()
|
2025-08-20 22:50:38 +02:00
|
|
|
|
|
|
|
|
// Computed property for current server URL
|
|
|
|
|
const currentServerUrl = computed(() => authStore.serverUrl)
|
2025-08-12 01:05:59 +02:00
|
|
|
const localSettings = reactive<AppSettings>({
|
|
|
|
|
soundEnabled: true,
|
|
|
|
|
speechEnabled: true,
|
|
|
|
|
ttsEnabled: true,
|
|
|
|
|
ttsRate: 1,
|
|
|
|
|
ttsPitch: 1,
|
|
|
|
|
ttsVolume: 1,
|
|
|
|
|
selectedVoiceURI: null,
|
|
|
|
|
defaultChannelId: null,
|
|
|
|
|
theme: 'auto'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const handleVoiceChange = () => {
|
|
|
|
|
const voice = availableVoices.value.find(v => v.voiceURI === selectedVoiceURI.value)
|
|
|
|
|
if (voice) {
|
|
|
|
|
setVoice(voice)
|
|
|
|
|
localSettings.selectedVoiceURI = voice.voiceURI
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const testSpeech = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await speak('This is a test of the text-to-speech system.', {
|
|
|
|
|
rate: localSettings.ttsRate,
|
|
|
|
|
pitch: localSettings.ttsPitch,
|
|
|
|
|
volume: localSettings.ttsVolume
|
|
|
|
|
})
|
|
|
|
|
} catch (error) {
|
|
|
|
|
toastStore.error('Speech test failed')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSave = async () => {
|
|
|
|
|
isSaving.value = true
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await appStore.updateSettings(localSettings)
|
|
|
|
|
toastStore.success('Settings saved successfully!')
|
|
|
|
|
emit('close')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to save settings:', error)
|
|
|
|
|
toastStore.error('Failed to save settings')
|
|
|
|
|
} finally {
|
|
|
|
|
isSaving.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-20 22:50:38 +02:00
|
|
|
const handleLogout = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await authStore.clearAuth()
|
|
|
|
|
toastStore.success('Logged out successfully')
|
|
|
|
|
emit('close')
|
|
|
|
|
router.push('/auth')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Logout failed:', error)
|
|
|
|
|
toastStore.error('Logout failed')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleResetData = async () => {
|
|
|
|
|
isResetting.value = true
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Clear all IndexedDB data
|
|
|
|
|
await clear()
|
|
|
|
|
|
|
|
|
|
// Clear stores
|
|
|
|
|
await authStore.clearAuth()
|
|
|
|
|
appStore.$reset()
|
|
|
|
|
|
|
|
|
|
toastStore.success('All data has been reset')
|
|
|
|
|
showResetConfirm.value = false
|
|
|
|
|
emit('close')
|
|
|
|
|
|
|
|
|
|
// Redirect to auth page
|
|
|
|
|
router.push('/auth')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Reset failed:', error)
|
|
|
|
|
toastStore.error('Failed to reset data')
|
|
|
|
|
} finally {
|
|
|
|
|
isResetting.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-12 01:05:59 +02:00
|
|
|
onMounted(() => {
|
|
|
|
|
// Copy current settings to local state
|
|
|
|
|
Object.assign(localSettings, appStore.settings)
|
|
|
|
|
|
|
|
|
|
// Set up voice selection
|
|
|
|
|
selectedVoiceURI.value = appStore.settings.selectedVoiceURI || ''
|
2025-08-22 19:01:45 +02:00
|
|
|
soundInput.value.focus();
|
2025-08-12 01:05:59 +02:00
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.settings-dialog {
|
|
|
|
|
padding: 1rem 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.settings-form {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 2rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.setting-group {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.setting-group h3 {
|
|
|
|
|
margin: 0;
|
|
|
|
|
font-size: 1.125rem;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #374151;
|
|
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
|
|
padding-bottom: 0.5rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.setting-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
padding: 0.5rem 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.setting-item label {
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #374151;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.checkbox {
|
|
|
|
|
width: 1.25rem;
|
|
|
|
|
height: 1.25rem;
|
|
|
|
|
accent-color: #646cff;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.select {
|
|
|
|
|
padding: 0.5rem 0.75rem;
|
|
|
|
|
border: 1px solid #d1d5db;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
background: white;
|
|
|
|
|
color: #111827;
|
|
|
|
|
font-size: 0.875rem;
|
|
|
|
|
min-width: 150px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.select:focus {
|
|
|
|
|
outline: none;
|
|
|
|
|
border-color: #646cff;
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(100, 108, 255, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider {
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 200px;
|
|
|
|
|
height: 4px;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
background: #e5e7eb;
|
|
|
|
|
outline: none;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
appearance: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider::-webkit-slider-thumb {
|
|
|
|
|
appearance: none;
|
|
|
|
|
width: 16px;
|
|
|
|
|
height: 16px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: #646cff;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
border: 2px solid white;
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slider::-moz-range-thumb {
|
|
|
|
|
width: 16px;
|
|
|
|
|
height: 16px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: #646cff;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
border: 2px solid white;
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
padding-top: 1rem;
|
|
|
|
|
border-top: 1px solid #e5e7eb;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-20 22:50:38 +02:00
|
|
|
.server-info {
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
background: #f9fafb;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
font-size: 0.875rem;
|
|
|
|
|
color: #374151;
|
|
|
|
|
word-break: break-all;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.setting-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
margin-top: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.confirm-overlay {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.confirm-dialog {
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 1.5rem;
|
|
|
|
|
max-width: 400px;
|
|
|
|
|
margin: 1rem;
|
|
|
|
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.confirm-dialog h3 {
|
|
|
|
|
margin: 0 0 1rem 0;
|
|
|
|
|
font-size: 1.125rem;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #dc2626;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.confirm-dialog p {
|
|
|
|
|
margin: 0 0 1.5rem 0;
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.confirm-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-12 01:05:59 +02:00
|
|
|
/* Dark mode */
|
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
|
|
|
.setting-group h3 {
|
|
|
|
|
color: rgba(255, 255, 255, 0.87);
|
|
|
|
|
border-bottom-color: #374151;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.setting-item label {
|
|
|
|
|
color: rgba(255, 255, 255, 0.87);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.select {
|
|
|
|
|
background: #374151;
|
|
|
|
|
color: rgba(255, 255, 255, 0.87);
|
|
|
|
|
border-color: #4b5563;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-actions {
|
|
|
|
|
border-top-color: #374151;
|
|
|
|
|
}
|
2025-08-20 22:50:38 +02:00
|
|
|
|
|
|
|
|
.server-info {
|
|
|
|
|
color: rgba(255, 255, 255, 0.87);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.confirm-overlay {
|
|
|
|
|
background: rgba(0, 0, 0, 0.8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.confirm-dialog {
|
|
|
|
|
background: #1f2937;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.confirm-dialog h3 {
|
|
|
|
|
color: rgba(255, 255, 255, 0.87);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.confirm-dialog p {
|
|
|
|
|
color: rgba(255, 255, 255, 0.6);
|
|
|
|
|
}
|
2025-08-12 01:05:59 +02:00
|
|
|
}
|
|
|
|
|
</style>
|