From eafa0549f7661e574d894c1b7b5bcce24893bafb Mon Sep 17 00:00:00 2001 From: admin_gitea Date: Tue, 4 Aug 2026 06:45:30 +0800 Subject: [PATCH] feat: unify public and admin interfaces --- .gitignore | 5 + src/App.vue | 184 +- src/api/index.js | 203 +- src/components/AboutPage.vue | 414 +-- src/components/Admin.vue | 3438 +++------------------- src/components/Dashboard.vue | 1712 ++--------- src/components/Home.vue | 888 +++--- src/components/IconSelector.vue | 1042 +++---- src/components/Login.vue | 613 ++-- src/components/Website.vue | 263 +- src/components/admin/AdminCollection.vue | 122 + src/components/admin/AdminConfig.vue | 213 ++ src/components/admin/AdminModal.vue | 90 + src/composables/useTheme.js | 36 + src/main.js | 33 +- src/router/index.js | 80 +- src/style.less | 322 +- 17 files changed, 3018 insertions(+), 6640 deletions(-) create mode 100644 .gitignore create mode 100644 src/components/admin/AdminCollection.vue create mode 100644 src/components/admin/AdminConfig.vue create mode 100644 src/components/admin/AdminModal.vue create mode 100644 src/composables/useTheme.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e7ba37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +*.exe +*.log +.DS_Store diff --git a/src/App.vue b/src/App.vue index 1171061..db395cc 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,104 +1,80 @@ - - - \ No newline at end of file + + + diff --git a/src/api/index.js b/src/api/index.js index f3334f7..e034a50 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -1,100 +1,103 @@ -import axios from 'axios' - -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api' - -const api = axios.create({ - baseURL: API_BASE_URL, - headers: { - 'Content-Type': 'application/json', - }, -}) - -// 请求拦截器 - 添加JWT token -api.interceptors.request.use( - (config) => { - const token = localStorage.getItem('token') - if (token) { - config.headers.Authorization = `Bearer ${token}` - } - return config - }, - (error) => { - return Promise.reject(error) - } -) - -// 响应拦截器 - 处理错误 -api.interceptors.response.use( - (response) => response, - (error) => { - if (error.response?.status === 401) { - localStorage.removeItem('token') - // 可以在这里跳转到登录页 - } - return Promise.reject(error) - } -) - -// 公开API -export const getSites = () => api.get('/sites') -export const getContacts = () => api.get('/contacts') -export const getSiteConfig = () => api.get('/config') -export const getFrontendConfig = () => api.get('/frontend-config') - -// 认证API -export const login = (username, password) => - api.post('/auth/login', { username, password }) - -// 管理API -export const adminAPI = { - // 站点管理 - getSites: () => api.get('/admin/sites'), - createSite: (data) => api.post('/admin/sites', data), - updateSite: (id, data) => api.put(`/admin/sites/${id}`, data), - deleteSite: (id) => api.delete(`/admin/sites/${id}`), - - // 联系方式管理 - getContacts: () => api.get('/admin/contacts'), - createContact: (data) => api.post('/admin/contacts', data), - updateContact: (id, data) => api.put(`/admin/contacts/${id}`, data), - deleteContact: (id) => api.delete(`/admin/contacts/${id}`), - - // 站点配置管理 - getSiteConfig: () => api.get('/admin/config'), - updateSiteConfig: (data) => api.put('/admin/config', data), - - // 文件上传 - uploadFile: (file) => { - const formData = new FormData() - formData.append('file', file) - return api.post('/admin/upload', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }) - }, - - // 统计API - getStats: () => api.get('/admin/stats'), - getChartData: (period) => api.get(`/admin/charts?period=${period}`), - getRecentVisits: (limit = 5) => api.get(`/admin/recent-visits?limit=${limit}`), - - // 热重载通知 - notifyConfigUpdate: () => api.post('/admin/notify-update'), - - // 用户管理 - changePassword: (oldPassword, newPassword) => - api.put('/admin/change-password', { oldPassword, newPassword }), - - // 日志API - getBackendLogs: (lines = 100) => api.get(`/admin/logs?lines=${lines}`), - - // 登录历史API - getLoginHistory: (limit = 20) => api.get(`/admin/login-history?limit=${limit}`), - - // 轮换文本配置API - getRotatingTexts: () => api.get('/api/rotating-texts'), - updateRotatingTexts: (texts) => api.put('/admin/rotating-texts', { texts }), -} - -export default api +import axios from 'axios' + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api' + +const api = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, +}) + +// 请求拦截器 - 添加JWT token +api.interceptors.request.use( + (config) => { + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + return Promise.reject(error) + } +) + +// 响应拦截器 - 处理错误 +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('token') + if (window.location.pathname.startsWith('/admin')) { + const redirect = encodeURIComponent(`${window.location.pathname}${window.location.search}`) + window.location.assign(`/login?redirect=${redirect}`) + } + } + return Promise.reject(error) + } +) + +// 公开API +export const getSites = () => api.get('/sites') +export const getContacts = () => api.get('/contacts') +export const getSiteConfig = () => api.get('/config') +export const getFrontendConfig = () => api.get('/frontend-config') + +// 认证API +export const login = (username, password) => + api.post('/auth/login', { username, password }) + +// 管理API +export const adminAPI = { + // 站点管理 + getSites: () => api.get('/admin/sites'), + createSite: (data) => api.post('/admin/sites', data), + updateSite: (id, data) => api.put(`/admin/sites/${id}`, data), + deleteSite: (id) => api.delete(`/admin/sites/${id}`), + + // 联系方式管理 + getContacts: () => api.get('/admin/contacts'), + createContact: (data) => api.post('/admin/contacts', data), + updateContact: (id, data) => api.put(`/admin/contacts/${id}`, data), + deleteContact: (id) => api.delete(`/admin/contacts/${id}`), + + // 站点配置管理 + getSiteConfig: () => api.get('/admin/config'), + updateSiteConfig: (data) => api.put('/admin/config', data), + + // 文件上传 + uploadFile: (file) => { + const formData = new FormData() + formData.append('file', file) + return api.post('/admin/upload', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + }, + + // 统计API + getStats: () => api.get('/admin/stats'), + getChartData: (period) => api.get(`/admin/charts?period=${period}`), + getRecentVisits: (limit = 5) => api.get(`/admin/recent-visits?limit=${limit}`), + + // 热重载通知 + notifyConfigUpdate: () => api.post('/admin/notify-update'), + + // 用户管理 + changePassword: (oldPassword, newPassword) => + api.put('/admin/change-password', { oldPassword, newPassword }), + + // 日志API + getBackendLogs: (lines = 100) => api.get(`/admin/logs?lines=${lines}`), + + // 登录历史API + getLoginHistory: (limit = 20) => api.get(`/admin/login-history?limit=${limit}`), + + // 轮换文本配置API + getRotatingTexts: () => api.get('/admin/rotating-texts'), + updateRotatingTexts: (texts) => api.put('/admin/rotating-texts', { texts }), +} + +export default api diff --git a/src/components/AboutPage.vue b/src/components/AboutPage.vue index 8dc5f3c..505a577 100644 --- a/src/components/AboutPage.vue +++ b/src/components/AboutPage.vue @@ -1,207 +1,207 @@ - - - - - \ No newline at end of file + + + + + diff --git a/src/components/Admin.vue b/src/components/Admin.vue index d77bfd2..c2cbbca 100644 --- a/src/components/Admin.vue +++ b/src/components/Admin.vue @@ -1,2984 +1,454 @@ - - - - - + + + + + diff --git a/src/components/Dashboard.vue b/src/components/Dashboard.vue index a50d2cb..e38b891 100644 --- a/src/components/Dashboard.vue +++ b/src/components/Dashboard.vue @@ -1,1495 +1,339 @@ diff --git a/src/components/Home.vue b/src/components/Home.vue index 09e8147..be4c3ba 100644 --- a/src/components/Home.vue +++ b/src/components/Home.vue @@ -1,528 +1,360 @@ - - - - - \ No newline at end of file + + + + + diff --git a/src/components/IconSelector.vue b/src/components/IconSelector.vue index a5cfc25..dfe3832 100644 --- a/src/components/IconSelector.vue +++ b/src/components/IconSelector.vue @@ -1,517 +1,525 @@ - - - - - + + + + + diff --git a/src/components/Login.vue b/src/components/Login.vue index 4969631..f610ae5 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -1,403 +1,210 @@ - - - - - + + + + + diff --git a/src/components/Website.vue b/src/components/Website.vue index f916e44..a32b8a5 100644 --- a/src/components/Website.vue +++ b/src/components/Website.vue @@ -1,158 +1,105 @@ - - - - - - \ No newline at end of file + + + + + diff --git a/src/components/admin/AdminCollection.vue b/src/components/admin/AdminCollection.vue new file mode 100644 index 0000000..f9e98dd --- /dev/null +++ b/src/components/admin/AdminCollection.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/src/components/admin/AdminConfig.vue b/src/components/admin/AdminConfig.vue new file mode 100644 index 0000000..722e8a0 --- /dev/null +++ b/src/components/admin/AdminConfig.vue @@ -0,0 +1,213 @@ + + + + + diff --git a/src/components/admin/AdminModal.vue b/src/components/admin/AdminModal.vue new file mode 100644 index 0000000..d1ffdac --- /dev/null +++ b/src/components/admin/AdminModal.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/composables/useTheme.js b/src/composables/useTheme.js new file mode 100644 index 0000000..607c2f4 --- /dev/null +++ b/src/composables/useTheme.js @@ -0,0 +1,36 @@ +import { computed, ref } from 'vue' + +const isDarkMode = ref(false) +let initialized = false + +const applyTheme = (dark) => { + isDarkMode.value = Boolean(dark) + document.documentElement.classList.toggle('dark-mode', isDarkMode.value) + document.body.classList.toggle('dark-mode', isDarkMode.value) + document.documentElement.style.colorScheme = isDarkMode.value ? 'dark' : 'light' +} + +export const initializeTheme = () => { + if (initialized || typeof window === 'undefined') return + + const savedTheme = localStorage.getItem('darkMode') + const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)').matches + applyTheme(savedTheme === null ? prefersDark : savedTheme === 'true') + initialized = true +} + +export const useTheme = () => { + initializeTheme() + + const toggleTheme = () => { + applyTheme(!isDarkMode.value) + localStorage.setItem('darkMode', String(isDarkMode.value)) + } + + return { + isDarkMode, + themeIcon: computed(() => (isDarkMode.value ? 'fas fa-sun' : 'fas fa-moon')), + themeLabel: computed(() => (isDarkMode.value ? '切换到浅色模式' : '切换到深色模式')), + toggleTheme, + } +} diff --git a/src/main.js b/src/main.js index 37563ae..c6d85bc 100644 --- a/src/main.js +++ b/src/main.js @@ -1,16 +1,17 @@ -import { createApp } from 'vue'; -import App from './App.vue'; -import './style.less'; -import { MotionPlugin } from '@vueuse/motion'; -import router from './router'; -import { loadAndApplyFrontendConfig } from './utils/frontendConfig'; - -// 加载前端配置 -loadAndApplyFrontendConfig(); - -const app = createApp(App); - -app.use(MotionPlugin); -app.use(router); - -app.mount('#app'); \ No newline at end of file +import { createApp } from 'vue'; +import App from './App.vue'; +import './style.less'; +import { MotionPlugin } from '@vueuse/motion'; +import router from './router'; +import { loadAndApplyFrontendConfig } from './utils/frontendConfig'; +import { initializeTheme } from './composables/useTheme'; + +initializeTheme(); +loadAndApplyFrontendConfig(); + +const app = createApp(App); + +app.use(MotionPlugin); +app.use(router); + +app.mount('#app'); diff --git a/src/router/index.js b/src/router/index.js index 2ef95a4..4e678e9 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,34 +1,46 @@ -import { createRouter, createWebHistory } from 'vue-router' -import Home from '../components/Home.vue' -import Admin from '../components/Admin.vue' -import Login from '../components/Login.vue' - -const routes = [ - { - path: '/', - component: Home, - }, - { - path: '/admin', - component: Admin, - beforeEnter: (to, from, next) => { - const token = localStorage.getItem('token') - if (!token) { - next('/login') - } else { - next() - } - }, - }, - { - path: '/login', - component: Login, - }, -] - -const router = createRouter({ - history: createWebHistory(), - routes, -}) - -export default router +import { createRouter, createWebHistory } from 'vue-router' +import Home from '../components/Home.vue' +import Admin from '../components/Admin.vue' +import Login from '../components/Login.vue' + +const routes = [ + { + path: '/', + name: 'home', + component: Home, + }, + { + path: '/admin', + name: 'admin', + component: Admin, + beforeEnter: (to, from, next) => { + const token = localStorage.getItem('token') + if (!token) { + next('/login') + } else { + next() + } + }, + }, + { + path: '/login', + name: 'login', + component: Login, + beforeEnter: (to) => { + if (localStorage.getItem('token')) { + const redirect = typeof to.query.redirect === 'string' && to.query.redirect.startsWith('/') + ? to.query.redirect + : '/admin' + return redirect + } + return true + }, + }, +] + +const router = createRouter({ + history: createWebHistory(), + routes, +}) + +export default router diff --git a/src/style.less b/src/style.less index bdaf968..ec77c82 100644 --- a/src/style.less +++ b/src/style.less @@ -1,155 +1,167 @@ -:root { - --border-radius: 10px; - --text-size: 16px; - --icon-size: 20px; - --text-color: #333; - --background-color: #eee; - --background-color-rgb: 238, 238, 238; - --shadow-color: rgba(0, 0, 0, 0.2); - --border-color: rgb(193 193 193 / 50%); - --hover-link-color: #ffcc00; - --hover-other-color: #555; - --background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxOTInIGhlaWdodD0nMTkyJyB2aWV3Qm94PScwIDAgMTkyIDE5Mic+PHBhdGggZmlsbD0nI2QzZDNkMycgZmlsbC1vcGFjaXR5PScwLjUnIGQ9J00xOTIgMTV2MmExMSAxMSAwIDAgMC0xMSAxMWMwIDEuOTQgMS4xNiA0Ljc1IDIuNTMgNi4xMWwyLjM2IDIuMzZhNi45MyA2LjkzIDAgMCAxIDEuMjIgNy41NmwtLjQzLjg0YTguMDggOC4wOCAwIDAgMS02LjY2IDQuMTNIMTQ1djM1LjAyYTYuMSA2LjEgMCAwIDAgMy4wMyA0Ljg3bC44NC40M2MxLjU4Ljc5IDQgLjQgNS4yNC0uODVsMi4zNi0yLjM2YTEyLjA0IDEyLjA0IDAgMCAxIDcuNTEtMy4xMSAxMyAxMyAwIDEgMSAuMDIgMjYgMTIgMTIgMCAwIDEtNy41My0zLjExbC0yLjM2LTIuMzZhNC45MyA0LjkzIDAgMCAwLTUuMjQtLjg1bC0uODQuNDNhNi4xIDYuMSAwIDAgMC0zLjAzIDQuODdWMTQzaDM1LjAyYTguMDggOC4wOCAwIDAgMSA2LjY2IDQuMTNsLjQzLjg0YTYuOTEgNi45MSAwIDAgMS0xLjIyIDcuNTZsLTIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxODEgMTY0YTExIDExIDAgMCAwIDExIDExdjJhMTMgMTMgMCAwIDEtMTMtMTMgMTIgMTIgMCAwIDEgMy4xMS03LjUzbDIuMzYtMi4zNmE0LjkzIDQuOTMgMCAwIDAgLjg1LTUuMjRsLS40My0uODRhNi4xIDYuMSAwIDAgMC00Ljg3LTMuMDNIMTQ1djM1LjAyYTguMDggOC4wOCAwIDAgMS00LjEzIDYuNjZsLS44NC40M2E2LjkxIDYuOTEgMCAwIDEtNy41Ni0xLjIybC0yLjM2LTIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgMTI0IDE4MWExMSAxMSAwIDAgMC0xMSAxMWgtMmExMyAxMyAwIDAgMSAxMy0xM2MyLjQ3IDAgNS43OSAxLjM3IDcuNTMgMy4xMWwyLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwIDUuMjQuODVsLjg0LS40M2E2LjEgNi4xIDAgMCAwIDMuMDMtNC44N1YxNDVoLTM1LjAyYTguMDggOC4wOCAwIDAgMS02LjY2LTQuMTNsLS40My0uODRhNi45MSA2LjkxIDAgMCAxIDEuMjItNy41NmwyLjM2LTIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgMTA3IDEyNGExMSAxMSAwIDAgMC0yMiAwYzAgMS45NCAxLjE2IDQuNzUgMi41MyA2LjExbDIuMzYgMi4zNmE2LjkzIDYuOTMgMCAwIDEgMS4yMiA3LjU2bC0uNDMuODRhOC4wOCA4LjA4IDAgMCAxLTYuNjYgNC4xM0g0OXYzNS4wMmE2LjEgNi4xIDAgMCAwIDMuMDMgNC44N2wuODQuNDNjMS41OC43OSA0IC40IDUuMjQtLjg1bDIuMzYtMi4zNmExMi4wNCAxMi4wNCAwIDAgMSA3LjUxLTMuMTFBMTMgMTMgMCAwIDEgODEgMTkyaC0yYTExIDExIDAgMCAwLTExLTExYy0xLjk0IDAtNC43NSAxLjE2LTYuMTEgMi41M2wtMi4zNiAyLjM2YTYuOTMgNi45MyAwIDAgMS03LjU2IDEuMjJsLS44NC0uNDNhOC4wOCA4LjA4IDAgMCAxLTQuMTMtNi42NlYxNDVIMTEuOThhNi4xIDYuMSAwIDAgMC00Ljg3IDMuMDNsLS40My44NGMtLjc5IDEuNTgtLjQgNCAuODUgNS4yNGwyLjM2IDIuMzZhMTIuMDQgMTIuMDQgMCAwIDEgMy4xMSA3LjUxQTEzIDEzIDAgMCAxIDAgMTc3di0yYTExIDExIDAgMCAwIDExLTExYzAtMS45NC0xLjE2LTQuNzUtMi41My02LjExbC0yLjM2LTIuMzZhNi45MyA2LjkzIDAgMCAxLTEuMjItNy41NmwuNDMtLjg0YTguMDggOC4wOCAwIDAgMSA2LjY2LTQuMTNINDd2LTM1LjAyYTYuMSA2LjEgMCAwIDAtMy4wMy00Ljg3bC0uODQtLjQzYy0xLjU5LS44LTQtLjQtNS4yNC44NWwtMi4zNiAyLjM2QTEyIDEyIDAgMCAxIDI4IDEwOWExMyAxMyAwIDEgMSAwLTI2YzIuNDcgMCA1Ljc5IDEuMzcgNy41MyAzLjExbDIuMzYgMi4zNmE0Ljk0IDQuOTQgMCAwIDAgNS4yNC44NWwuODQtLjQzQTYuMSA2LjEgMCAwIDAgNDcgODQuMDJWNDlIMTEuOThhOC4wOCA4LjA4IDAgMCAxLTYuNjYtNC4xM2wtLjQzLS44NGE2LjkxIDYuOTEgMCAwIDEgMS4yMi03LjU2bDIuMzYtMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxMSAyOCAxMSAxMSAwIDAgMCAwIDE3di0yYTEzIDEzIDAgMCAxIDEzIDEzYzAgMi40Ny0xLjM3IDUuNzktMy4xMSA3LjUzbC0yLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwLS44NSA1LjI0bC40My44NEE2LjEgNi4xIDAgMCAwIDExLjk4IDQ3SDQ3VjExLjk4YTguMDggOC4wOCAwIDAgMSA0LjEzLTYuNjZsLjg0LS40M2E2LjkxIDYuOTEgMCAwIDEgNy41NiAxLjIybDIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCA2OCAxMSAxMSAxMSAwIDAgMCA3OSAwaDJhMTMgMTMgMCAwIDEtMTMgMTMgMTIgMTIgMCAwIDEtNy41My0zLjExbC0yLjM2LTIuMzZhNC45MyA0LjkzIDAgMCAwLTUuMjQtLjg1bC0uODQuNDNBNi4xIDYuMSAwIDAgMCA0OSAxMS45OFY0N2gzNS4wMmE4LjA4IDguMDggMCAwIDEgNi42NiA0LjEzbC40My44NGE2LjkxIDYuOTEgMCAwIDEtMS4yMiA3LjU2bC0yLjM2IDIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgODUgNjhhMTEgMTEgMCAwIDAgMjIgMGMwLTEuOTQtMS4xNi00Ljc1LTIuNTMtNi4xMWwtMi4zNi0yLjM2YTYuOTMgNi45MyAwIDAgMS0xLjIyLTcuNTZsLjQzLS44NGE4LjA4IDguMDggMCAwIDEgNi42Ni00LjEzSDE0M1YxMS45OGE2LjEgNi4xIDAgMCAwLTMuMDMtNC44N2wtLjg0LS40M2MtMS41OS0uOC00LS40LTUuMjQuODVsLTIuMzYgMi4zNkExMiAxMiAwIDAgMSAxMjQgMTNhMTMgMTMgMCAwIDEtMTMtMTNoMmExMSAxMSAwIDAgMCAxMSAxMWMxLjk0IDAgNC43NS0xLjE2IDYuMTEtMi41M2wyLjM2LTIuMzZhNi45MyA2LjkzIDAgMCAxIDcuNTYtMS4yMmwuODQuNDNhOC4wOCA4LjA4IDAgMCAxIDQuMTMgNi42NlY0N2gzNS4wMmE2LjEgNi4xIDAgMCAwIDQuODctMy4wM2wuNDMtLjg0Yy44LTEuNTkuNC00LS44NS01LjI0bC0yLjM2LTIuMzZBMTIgMTIgMCAwIDEgMTc5IDI4YTEzIDEzIDAgMCAxIDEzLTEzek04NC4wMiAxNDNhNi4xIDYuMSAwIDAgMCA0Ljg3LTMuMDNsLjQzLS44NGMuOC0xLjU5LjQtNC0uODUtNS4yNGwtMi4zNi0yLjM2QTEyIDEyIDAgMCAxIDgzIDEyNGExMyAxMyAwIDEgMSAyNiAwYzAgMi40Ny0xLjM3IDUuNzktMy4xMSA3LjUzbC0yLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwLS44NSA1LjI0bC40My44NGE2LjEgNi4xIDAgMCAwIDQuODcgMy4wM0gxNDN2LTM1LjAyYTguMDggOC4wOCAwIDAgMSA0LjEzLTYuNjZsLjg0LS40M2E2LjkxIDYuOTEgMCAwIDEgNy41NiAxLjIybDIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxNjQgMTA3YTExIDExIDAgMCAwIDAtMjJjLTEuOTQgMC00Ljc1IDEuMTYtNi4xMSAyLjUzbC0yLjM2IDIuMzZhNi45MyA2LjkzIDAgMCAxLTcuNTYgMS4yMmwtLjg0LS40M2E4LjA4IDguMDggMCAwIDEtNC4xMy02LjY2VjQ5aC0zNS4wMmE2LjEgNi4xIDAgMCAwLTQuODcgMy4wM2wtLjQzLjg0Yy0uNzkgMS41OC0uNCA0IC44NSA1LjI0bDIuMzYgMi4zNmExMi4wNCAxMi4wNCAwIDAgMSAzLjExIDcuNTFBMTMgMTMgMCAxIDEgODMgNjhhMTIgMTIgMCAwIDEgMy4xMS03LjUzbDIuMzYtMi4zNmE0LjkzIDQuOTMgMCAwIDAgLjg1LTUuMjRsLS40My0uODRBNi4xIDYuMSAwIDAgMCA4NC4wMiA0OUg0OXYzNS4wMmE4LjA4IDguMDggMCAwIDEtNC4xMyA2LjY2bC0uODQuNDNhNi45MSA2LjkxIDAgMCAxLTcuNTYtMS4yMmwtMi4zNi0yLjM2QTEwLjA2IDEwLjA2IDAgMCAwIDI4IDg1YTExIDExIDAgMCAwIDAgMjJjMS45NCAwIDQuNzUtMS4xNiA2LjExLTIuNTNsMi4zNi0yLjM2YTYuOTMgNi45MyAwIDAgMSA3LjU2LTEuMjJsLjg0LjQzYTguMDggOC4wOCAwIDAgMSA0LjEzIDYuNjZWMTQzaDM1LjAyeic+PC9wYXRoPjwvc3ZnPg==); -} - -.dark-mode { - --text-color: #eee; - --background-color: #333; - --background-color-rgb: 51, 51, 51; - --shadow-color: rgba(255, 255, 255, 0.2); - --border-color: rgb(131 131 131 / 50%); - --background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxOTInIGhlaWdodD0nMTkyJyB2aWV3Qm94PScwIDAgMTkyIDE5Mic+PHBhdGggZmlsbD0nIzVhNWE1YScgZmlsbC1vcGFjaXR5PScwLjUnIGQ9J00xOTIgMTV2MmExMSAxMSAwIDAgMC0xMSAxMWMwIDEuOTQgMS4xNiA0Ljc1IDIuNTMgNi4xMWwyLjM2IDIuMzZhNi45MyA2LjkzIDAgMCAxIDEuMjIgNy41NmwtLjQzLjg0YTguMDggOC4wOCAwIDAgMS02LjY2IDQuMTNIMTQ1djM1LjAyYTYuMSA2LjEgMCAwIDAgMy4wMyA0Ljg3bC44NC40M2MxLjU4Ljc5IDQgLjQgNS4yNC0uODVsMi4zNi0yLjM2YTEyLjA0IDEyLjA0IDAgMCAxIDcuNTEtMy4xMSAxMyAxMyAwIDEgMSAuMDIgMjYgMTIgMTIgMCAwIDEtNy41My0zLjExbC0yLjM2LTIuMzZhNC45MyA0LjkzIDAgMCAwLTUuMjQtLjg1bC0uODQuNDNhNi4xIDYuMSAwIDAgMC0zLjAzIDQuODdWMTQzaDM1LjAyYTguMDggOC4wOCAwIDAgMSA2LjY2IDQuMTNsLjQzLjg0YTYuOTEgNi45MSAwIDAgMS0xLjIyIDcuNTZsLTIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxODEgMTY0YTExIDExIDAgMCAwIDExIDExdjJhMTMgMTMgMCAwIDEtMTMtMTMgMTIgMTIgMCAwIDEgMy4xMS03LjUzbDIuMzYtMi4zNmE0LjkzIDQuOTMgMCAwIDAgLjg1LTUuMjRsLS40My0uODRhNi4xIDYuMSAwIDAgMC00Ljg3LTMuMDNIMTQ1djM1LjAyYTguMDggOC4wOCAwIDAgMS00LjEzIDYuNjZsLS44NC40M2E2LjkxIDYuOTEgMCAwIDEtNy41Ni0xLjIybC0yLjM2LTIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgMTI0IDE4MWExMSAxMSAwIDAgMC0xMSAxMWgtMmExMyAxMyAwIDAgMSAxMy0xM2MyLjQ3IDAgNS43OSAxLjM3IDcuNTMgMy4xMWwyLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwIDUuMjQuODVsLjg0LS40M2E2LjEgNi4xIDAgMCAwIDMuMDMtNC44N1YxNDVoLTM1LjAyYTguMDggOC4wOCAwIDAgMS02LjY2LTQuMTNsLS40My0uODRhNi45MSA2LjkxIDAgMCAxIDEuMjItNy41NmwyLjM2LTIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgMTA3IDEyNGExMSAxMSAwIDAgMC0yMiAwYzAgMS45NCAxLjE2IDQuNzUgMi41MyA2LjExbDIuMzYgMi4zNmE2LjkzIDYuOTMgMCAwIDEgMS4yMiA3LjU2bC0uNDMuODRhOC4wOCA4LjA4IDAgMCAxLTYuNjYgNC4xM0g0OXYzNS4wMmE2LjEgNi4xIDAgMCAwIDMuMDMgNC44N2wuODQuNDNjMS41OC43OSA0IC40IDUuMjQtLjg1bDIuMzYtMi4zNmExMi4wNCAxMi4wNCAwIDAgMSA3LjUxLTMuMTFBMTMgMTMgMCAwIDEgODEgMTkyaC0yYTExIDExIDAgMCAwLTExLTExYy0xLjk0IDAtNC43NSAxLjE2LTYuMTEgMi41M2wtMi4zNiAyLjM2YTYuOTMgNi45MyAwIDAgMS03LjU2IDEuMjJsLS44NC0uNDNhOC4wOCA4LjA4IDAgMCAxLTQuMTMtNi42NlYxNDVIMTEuOThhNi4xIDYuMSAwIDAgMC00Ljg3IDMuMDNsLS40My44NGMtLjc5IDEuNTgtLjQgNCAuODUgNS4yNGwyLjM2IDIuMzZhMTIuMDQgMTIuMDQgMCAwIDEgMy4xMSA3LjUxQTEzIDEzIDAgMCAxIDAgMTc3di0yYTExIDExIDAgMCAwIDExLTExYzAtMS45NC0xLjE2LTQuNzUtMi41My02LjExbC0yLjM2LTIuMzZhNi45MyA2LjkzIDAgMCAxLTEuMjItNy41NmwuNDMtLjg0YTguMDggOC4wOCAwIDAgMSA2LjY2LTQuMTNINDd2LTM1LjAyYTYuMSA2LjEgMCAwIDAtMy4wMy00Ljg3bC0uODQtLjQzYy0xLjU5LS44LTQtLjQtNS4yNC44NWwtMi4zNiAyLjM2QTEyIDEyIDAgMCAxIDI4IDEwOWExMyAxMyAwIDEgMSAwLTI2YzIuNDcgMCA1Ljc5IDEuMzcgNy41MyAzLjExbDIuMzYgMi4zNmE0Ljk0IDQuOTQgMCAwIDAgNS4yNC44NWwuODQtLjQzQTYuMSA2LjEgMCAwIDAgNDcgODQuMDJWNDlIMTEuOThhOC4wOCA4LjA4IDAgMCAxLTYuNjYtNC4xM2wtLjQzLS44NGE2LjkxIDYuOTEgMCAwIDEgMS4yMi03LjU2bDIuMzYtMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxMSAyOCAxMSAxMSAwIDAgMCAwIDE3di0yYTEzIDEzIDAgMCAxIDEzIDEzYzAgMi40Ny0xLjM3IDUuNzktMy4xMSA3LjUzbC0yLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwLS44NSA1LjI0bC40My44NEE2LjEgNi4xIDAgMCAwIDExLjk4IDQ3SDQ3VjExLjk4YTguMDggOC4wOCAwIDAgMSA0LjEzLTYuNjZsLjg0LS40M2E2LjkxIDYuOTEgMCAwIDEgNy41NiAxLjIybDIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCA2OCAxMSAxMSAxMSAwIDAgMCA3OSAwaDJhMTMgMTMgMCAwIDEtMTMgMTMgMTIgMTIgMCAwIDEtNy41My0zLjExbC0yLjM2LTIuMzZhNC45MyA0LjkzIDAgMCAwLTUuMjQtLjg1bC0uODQuNDNBNi4xIDYuMSAwIDAgMCA0OSAxMS45OFY0N2gzNS4wMmE4LjA4IDguMDggMCAwIDEgNi42NiA0LjEzbC40My44NGE2LjkxIDYuOTEgMCAwIDEtMS4yMiA3LjU2bC0yLjM2IDIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgODUgNjhhMTEgMTEgMCAwIDAgMjIgMGMwLTEuOTQtMS4xNi00Ljc1LTIuNTMtNi4xMWwtMi4zNi0yLjM2YTYuOTMgNi45MyAwIDAgMS0xLjIyLTcuNTZsLjQzLS44NGE4LjA4IDguMDggMCAwIDEgNi42Ni00LjEzSDE0M1YxMS45OGE2LjEgNi4xIDAgMCAwLTMuMDMtNC44N2wtLjg0LS40M2MtMS41OS0uOC00LS40LTUuMjQuODVsLTIuMzYgMi4zNkExMiAxMiAwIDAgMSAxMjQgMTNhMTMgMTMgMCAwIDEtMTMtMTNoMmExMSAxMSAwIDAgMCAxMSAxMWMxLjk0IDAgNC43NS0xLjE2IDYuMTEtMi41M2wyLjM2LTIuMzZhNi45MyA2LjkzIDAgMCAxIDcuNTYtMS4yMmwuODQuNDNhOC4wOCA4LjA4IDAgMCAxIDQuMTMgNi42NlY0N2gzNS4wMmE2LjEgNi4xIDAgMCAwIDQuODctMy4wM2wuNDMtLjg0Yy44LTEuNTkuNC00LS44NS01LjI0bC0yLjM2LTIuMzZBMTIgMTIgMCAwIDEgMTc5IDI4YTEzIDEzIDAgMCAxIDEzLTEzek04NC4wMiAxNDNhNi4xIDYuMSAwIDAgMCA0Ljg3LTMuMDNsLjQzLS44NGMuOC0xLjU5LjQtNC0uODUtNS4yNGwtMi4zNi0yLjM2QTEyIDEyIDAgMCAxIDgzIDEyNGExMyAxMyAwIDEgMSAyNiAwYzAgMi40Ny0xLjM3IDUuNzktMy4xMSA3LjUzbC0yLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwLS44NSA1LjI0bC40My44NGE2LjEgNi4xIDAgMCAwIDQuODcgMy4wM0gxNDN2LTM1LjAyYTguMDggOC4wOCAwIDAgMSA0LjEzLTYuNjZsLjg0LS40M2E2LjkxIDYuOTEgMCAwIDEgNy41NiAxLjIybDIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxNjQgMTA3YTExIDExIDAgMCAwIDAtMjJjLTEuOTQgMC00Ljc1IDEuMTYtNi4xMSAyLjUzbC0yLjM2IDIuMzZhNi45MyA2LjkzIDAgMCAxLTcuNTYgMS4yMmwtLjg0LS40M2E4LjA4IDguMDggMCAwIDEtNC4xMy02LjY2VjQ5aC0zNS4wMmE2LjEgNi4xIDAgMCAwLTQuODcgMy4wM2wtLjQzLjg0Yy0uNzkgMS41OC0uNCA0IC44NSA1LjI0bDIuMzYgMi4zNmExMi4wNCAxMi4wNCAwIDAgMSAzLjExIDcuNTFBMTMgMTMgMCAxIDEgODMgNjhhMTIgMTIgMCAwIDEgMy4xMS03LjUzbDIuMzYtMi4zNmE0LjkzIDQuOTMgMCAwIDAgLjg1LTUuMjRsLS40My0uODRBNi4xIDYuMSAwIDAgMCA4NC4wMiA0OUg0OXYzNS4wMmE4LjA4IDguMDggMCAwIDEtNC4xMyA2LjY2bC0uODQuNDNhNi45MSA2LjkxIDAgMCAxLTcuNTYtMS4yMmwtMi4zNi0yLjM2QTEwLjA2IDEwLjA2IDAgMCAwIDI4IDg1YTExIDExIDAgMCAwIDAgMjJjMS45NCAwIDQuNzUtMS4xNiA2LjExLTIuNTNsMi4zNi0yLjM2YTYuOTMgNi45MyAwIDAgMSA3LjU2LTEuMjJsLjg0LjQzYTguMDggOC4wOCAwIDAgMSA0LjEzIDYuNjZWMTQzaDM1LjAyeic+PC9wYXRoPjwvc3ZnPg==); -} - -* { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - box-sizing: border-box; -} - -/* 顶部呼吸弹窗通知动画 - 全局样式 */ -@keyframes slideDownAndBreathe { - 0% { - transform: translate(-50%, -100%); - opacity: 0; - } - 50% { - transform: translate(-50%, 0); - opacity: 1; - } - 60% { - transform: translate(-50%, 0) scale(1.03); - } - 70% { - transform: translate(-50%, 0) scale(1); - } - 80% { - transform: translate(-50%, 0) scale(1.02); - } - 100% { - transform: translate(-50%, 0) scale(1); - opacity: 1; - } -} - -@keyframes slideUpAndFade { - from { - transform: translate(-50%, 0) scale(1); - opacity: 1; - } - to { - transform: translate(-50%, -100%) scale(0.9); - opacity: 0; - } -} - -/* 确保通知在全局样式下正确显示 */ -.top-notification { - position: fixed !important; - top: 0 !important; - left: 50% !important; - z-index: 10000 !important; -} - -a { - text-decoration: none; -} - -ul { - padding: 0; - list-style: none; - margin: 0; -} - -body, html { - height: 100%; - margin: 0; - display: flex; - flex-direction: column; - font-family: 'HarmonyOS_Regular', 'MiSans', 'HarmonyOS Sans SC', sans-serif; - color: var(--text-color); - overflow: hidden; -} - -body.dark-mode { - filter: brightness(0.7); -} - -/* App 页面 */ -#app { - flex: 1; - display: flex; - flex-direction: column; - padding: 0 10px; -} - -.background { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - display: flex; - align-items: center; - justify-content: center; - z-index: -2; - background: var(--background-color) var(--background-image) repeat; -} - - -footer { - padding: 10px 0; - display: flex; - justify-content: center; - margin: 0; - font-size: var(--text-size); - gap: 8px; - - a { - color: var(--text-color); - transition: all 0.3s ease-in-out; - &:hover { - color: var(--hover-link-color); - } - } -} - -.police_link { - display: flex; - align-items: center; - - .police_img { - display: inline-block; - width: 20px; - height: 20px; - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAACglBMVEUAAAD89dn+/ODz6KrcrGreqFfYrn3x16769M3oxnyaaVzhs2ifaFXbrGLkvFnpyF7v2X/kwm3cp1nhsGfqw3rntVzjrFPt3oDjvGnbnFGti3q0lH7ktoLryXn9v1T4znr/74bnvGz034v+2I/ktoDz6ZLkwY/Dfz7buoftzYbq2IPr0pjs3bLv6KPRrnbKhFv/+dDZr4Lx38f/+cH/95f42oL7/97s2Y3++uzTk3AAAXgCAWbmulrdmUXWHRcBAm7uzWbpwmLsxl3ktV3qvFr/0ljmwFHksE7fo0raSijXPSDWAgjPAAJrdoeUj4EAAICRiXb143Pyy27w1Gz+z2XtymH/217qyVy7jluTeFrrwFfislP+xVJRNlHpuFDpsErVkEbysETajkPpgj7ThT3ilDvXdTg5FDjdhTXtcTDWZTC7TCbuXiPTOCLJPR/TLhzJJxP9LA+0Bw/QFg2Ej5kcPpdyhZSptJEACJFpfo4AG44XMInFvYfTvIejmYRyeoJzdoK9un7FrnwAEHp8enny2HjWwHjKtnhcX3jYzHfu2HWsonPNwnH70m9WTm8AAW//723pym3dtmm0o2ZeWWb/4mPtwmJuYmL/vmB5ZGAMAF2njVn4xFjYnli0mVjiqVfyyVbmtlbXkVPlvFLNrFKjfVLuvlBgHlDsuU9ODk7/2E02Gk3jqkqEaUr/tUngjkf7n0b6xERCJETdn0K1gD/ooD3Ulj3TZT3WjjzOeDqBWDr3pDlMADnbbTf2gjbkbzaTYDZpAjbplzTEazPXXzOeXzDMPi38jizJWSrVSCrzfCjVdCjTQCbFUiTlYCPiRh7SIRrRDBG/ABHBDAvEGArJAAJrcyAdAAAAOnRSTlMAGghC4MasaVv7+/n39fTw7Ozr5+bk5OHg0tHLx8TDwb68urKxq5+cmJWTjIN8b11MSEFBNTApJyEUZZQtrgAAAW1JREFUGNNigAJGdmkpdkYGZGDMxnN019qlXGwGCDGmiPQDh89lTupOj2CCa5WPjTzlnevps6E8Vhkqasa9esrxC3b5nsWXZ/huEjEHC5oE+M/O8c63u5h7NafRPsAUrFnOz2rLeW87Ly87H6/O/hYFC6Cg3u46/3n7fE5sc1tfnNlj7+eqABTUONRUMX1jdl5W2t6kqTOtahLVgIIsGUGl9tnJJ/OOnNm/3bq6NZUFKAgY7851ZVZFhVln05ILL1lXxqRIAAVVNofUVu0pulJQ4HnsYHN7b7QmUFAmaX5IiZW19crls6yt6sOc18gCBTnnLkoNaov0nbyko69h2lZbdwGgIMdij4TTc8JWxSS6TnDNSMgOZ2YAifK7pexwcHG3cQi2tZ0oCBZjMBIOd1oY5eQY6uJhE+woBBFUCuxydnZxd4yz9bCxcQhUhYSmFl/cgmXxK+KjnNxCxXVhAWqpr84qKSbKqqhtCOYDALPYc2hoS0J5AAAAAElFTkSuQmCC"); - } -} - -@media screen and (max-width: 768px) { - .police_link { - display: none; - } -} \ No newline at end of file +:root { + --border-radius: 10px; + --text-size: 16px; + --icon-size: 20px; + --text-color: #333; + --background-color: #eee; + --background-color-rgb: 238, 238, 238; + --shadow-color: rgba(0, 0, 0, 0.2); + --border-color: rgb(193 193 193 / 50%); + --hover-link-color: #ffcc00; + --hover-link-color-rgb: 255, 204, 0; + --hover-other-color: #555; + --surface-color: rgba(238, 238, 238, 0.86); + --surface-solid: #f6f6f4; + --surface-muted: rgba(255, 255, 255, 0.38); + --text-muted: #6d6d68; + --success-color: #238636; + --danger-color: #c9362b; + --warning-color: #a76500; + --focus-ring: rgba(255, 204, 0, 0.34); + --control-height: 40px; + --background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxOTInIGhlaWdodD0nMTkyJyB2aWV3Qm94PScwIDAgMTkyIDE5Mic+PHBhdGggZmlsbD0nI2QzZDNkMycgZmlsbC1vcGFjaXR5PScwLjUnIGQ9J00xOTIgMTV2MmExMSAxMSAwIDAgMC0xMSAxMWMwIDEuOTQgMS4xNiA0Ljc1IDIuNTMgNi4xMWwyLjM2IDIuMzZhNi45MyA2LjkzIDAgMCAxIDEuMjIgNy41NmwtLjQzLjg0YTguMDggOC4wOCAwIDAgMS02LjY2IDQuMTNIMTQ1djM1LjAyYTYuMSA2LjEgMCAwIDAgMy4wMyA0Ljg3bC44NC40M2MxLjU4Ljc5IDQgLjQgNS4yNC0uODVsMi4zNi0yLjM2YTEyLjA0IDEyLjA0IDAgMCAxIDcuNTEtMy4xMSAxMyAxMyAwIDEgMSAuMDIgMjYgMTIgMTIgMCAwIDEtNy41My0zLjExbC0yLjM2LTIuMzZhNC45MyA0LjkzIDAgMCAwLTUuMjQtLjg1bC0uODQuNDNhNi4xIDYuMSAwIDAgMC0zLjAzIDQuODdWMTQzaDM1LjAyYTguMDggOC4wOCAwIDAgMSA2LjY2IDQuMTNsLjQzLjg0YTYuOTEgNi45MSAwIDAgMS0xLjIyIDcuNTZsLTIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxODEgMTY0YTExIDExIDAgMCAwIDExIDExdjJhMTMgMTMgMCAwIDEtMTMtMTMgMTIgMTIgMCAwIDEgMy4xMS03LjUzbDIuMzYtMi4zNmE0LjkzIDQuOTMgMCAwIDAgLjg1LTUuMjRsLS40My0uODRhNi4xIDYuMSAwIDAgMC00Ljg3LTMuMDNIMTQ1djM1LjAyYTguMDggOC4wOCAwIDAgMS00LjEzIDYuNjZsLS44NC40M2E2LjkxIDYuOTEgMCAwIDEtNy41Ni0xLjIybC0yLjM2LTIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgMTI0IDE4MWExMSAxMSAwIDAgMC0xMSAxMWgtMmExMyAxMyAwIDAgMSAxMy0xM2MyLjQ3IDAgNS43OSAxLjM3IDcuNTMgMy4xMWwyLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwIDUuMjQuODVsLjg0LS40M2E2LjEgNi4xIDAgMCAwIDMuMDMtNC44N1YxNDVoLTM1LjAyYTguMDggOC4wOCAwIDAgMS02LjY2LTQuMTNsLS40My0uODRhNi45MSA2LjkxIDAgMCAxIDEuMjItNy41NmwyLjM2LTIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgMTA3IDEyNGExMSAxMSAwIDAgMC0yMiAwYzAgMS45NCAxLjE2IDQuNzUgMi41MyA2LjExbDIuMzYgMi4zNmE2LjkzIDYuOTMgMCAwIDEgMS4yMiA3LjU2bC0uNDMuODRhOC4wOCA4LjA4IDAgMCAxLTYuNjYgNC4xM0g0OXYzNS4wMmE2LjEgNi4xIDAgMCAwIDMuMDMgNC44N2wuODQuNDNjMS41OC43OSA0IC40IDUuMjQtLjg1bDIuMzYtMi4zNmExMi4wNCAxMi4wNCAwIDAgMSA3LjUxLTMuMTFBMTMgMTMgMCAwIDEgODEgMTkyaC0yYTExIDExIDAgMCAwLTExLTExYy0xLjk0IDAtNC43NSAxLjE2LTYuMTEgMi41M2wtMi4zNiAyLjM2YTYuOTMgNi45MyAwIDAgMS03LjU2IDEuMjJsLS44NC0uNDNhOC4wOCA4LjA4IDAgMCAxLTQuMTMtNi42NlYxNDVIMTEuOThhNi4xIDYuMSAwIDAgMC00Ljg3IDMuMDNsLS40My44NGMtLjc5IDEuNTgtLjQgNCAuODUgNS4yNGwyLjM2IDIuMzZhMTIuMDQgMTIuMDQgMCAwIDEgMy4xMSA3LjUxQTEzIDEzIDAgMCAxIDAgMTc3di0yYTExIDExIDAgMCAwIDExLTExYzAtMS45NC0xLjE2LTQuNzUtMi41My02LjExbC0yLjM2LTIuMzZhNi45MyA2LjkzIDAgMCAxLTEuMjItNy41NmwuNDMtLjg0YTguMDggOC4wOCAwIDAgMSA2LjY2LTQuMTNINDd2LTM1LjAyYTYuMSA2LjEgMCAwIDAtMy4wMy00Ljg3bC0uODQtLjQzYy0xLjU5LS44LTQtLjQtNS4yNC44NWwtMi4zNiAyLjM2QTEyIDEyIDAgMCAxIDI4IDEwOWExMyAxMyAwIDEgMSAwLTI2YzIuNDcgMCA1Ljc5IDEuMzcgNy41MyAzLjExbDIuMzYgMi4zNmE0Ljk0IDQuOTQgMCAwIDAgNS4yNC44NWwuODQtLjQzQTYuMSA2LjEgMCAwIDAgNDcgODQuMDJWNDlIMTEuOThhOC4wOCA4LjA4IDAgMCAxLTYuNjYtNC4xM2wtLjQzLS44NGE2LjkxIDYuOTEgMCAwIDEgMS4yMi03LjU2bDIuMzYtMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxMSAyOCAxMSAxMSAwIDAgMCAwIDE3di0yYTEzIDEzIDAgMCAxIDEzIDEzYzAgMi40Ny0xLjM3IDUuNzktMy4xMSA3LjUzbC0yLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwLS44NSA1LjI0bC40My44NEE2LjEgNi4xIDAgMCAwIDExLjk4IDQ3SDQ3VjExLjk4YTguMDggOC4wOCAwIDAgMSA0LjEzLTYuNjZsLjg0LS40M2E2LjkxIDYuOTEgMCAwIDEgNy41NiAxLjIybDIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCA2OCAxMSAxMSAxMSAwIDAgMCA3OSAwaDJhMTMgMTMgMCAwIDEtMTMgMTMgMTIgMTIgMCAwIDEtNy41My0zLjExbC0yLjM2LTIuMzZhNC45MyA0LjkzIDAgMCAwLTUuMjQtLjg1bC0uODQuNDNBNi4xIDYuMSAwIDAgMCA0OSAxMS45OFY0N2gzNS4wMmE4LjA4IDguMDggMCAwIDEgNi42NiA0LjEzbC40My44NGE2LjkxIDYuOTEgMCAwIDEtMS4yMiA3LjU2bC0yLjM2IDIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgODUgNjhhMTEgMTEgMCAwIDAgMjIgMGMwLTEuOTQtMS4xNi00Ljc1LTIuNTMtNi4xMWwtMi4zNi0yLjM2YTYuOTMgNi45MyAwIDAgMS0xLjIyLTcuNTZsLjQzLS44NGE4LjA4IDguMDggMCAwIDEgNi42Ni00LjEzSDE0M1YxMS45OGE2LjEgNi4xIDAgMCAwLTMuMDMtNC44N2wtLjg0LS40M2MtMS41OS0uOC00LS40LTUuMjQuODVsLTIuMzYgMi4zNkExMiAxMiAwIDAgMSAxMjQgMTNhMTMgMTMgMCAwIDEtMTMtMTNoMmExMSAxMSAwIDAgMCAxMSAxMWMxLjk0IDAgNC43NS0xLjE2IDYuMTEtMi41M2wyLjM2LTIuMzZhNi45MyA2LjkzIDAgMCAxIDcuNTYtMS4yMmwuODQuNDNhOC4wOCA4LjA4IDAgMCAxIDQuMTMgNi42NlY0N2gzNS4wMmE2LjEgNi4xIDAgMCAwIDQuODctMy4wM2wuNDMtLjg0Yy44LTEuNTkuNC00LS44NS01LjI0bC0yLjM2LTIuMzZBMTIgMTIgMCAwIDEgMTc5IDI4YTEzIDEzIDAgMCAxIDEzLTEzek04NC4wMiAxNDNhNi4xIDYuMSAwIDAgMCA0Ljg3LTMuMDNsLjQzLS44NGMuOC0xLjU5LjQtNC0uODUtNS4yNGwtMi4zNi0yLjM2QTEyIDEyIDAgMCAxIDgzIDEyNGExMyAxMyAwIDEgMSAyNiAwYzAgMi40Ny0xLjM3IDUuNzktMy4xMSA3LjUzbC0yLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwLS44NSA1LjI0bC40My44NGE2LjEgNi4xIDAgMCAwIDQuODcgMy4wM0gxNDN2LTM1LjAyYTguMDggOC4wOCAwIDAgMSA0LjEzLTYuNjZsLjg0LS40M2E2LjkxIDYuOTEgMCAwIDEgNy41NiAxLjIybDIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxNjQgMTA3YTExIDExIDAgMCAwIDAtMjJjLTEuOTQgMC00Ljc1IDEuMTYtNi4xMSAyLjUzbC0yLjM2IDIuMzZhNi45MyA2LjkzIDAgMCAxLTcuNTYgMS4yMmwtLjg0LS40M2E4LjA4IDguMDggMCAwIDEtNC4xMy02LjY2VjQ5aC0zNS4wMmE2LjEgNi4xIDAgMCAwLTQuODcgMy4wM2wtLjQzLjg0Yy0uNzkgMS41OC0uNCA0IC44NSA1LjI0bDIuMzYgMi4zNmExMi4wNCAxMi4wNCAwIDAgMSAzLjExIDcuNTFBMTMgMTMgMCAxIDEgODMgNjhhMTIgMTIgMCAwIDEgMy4xMS03LjUzbDIuMzYtMi4zNmE0LjkzIDQuOTMgMCAwIDAgLjg1LTUuMjRsLS40My0uODRBNi4xIDYuMSAwIDAgMCA4NC4wMiA0OUg0OXYzNS4wMmE4LjA4IDguMDggMCAwIDEtNC4xMyA2LjY2bC0uODQuNDNhNi45MSA2LjkxIDAgMCAxLTcuNTYtMS4yMmwtMi4zNi0yLjM2QTEwLjA2IDEwLjA2IDAgMCAwIDI4IDg1YTExIDExIDAgMCAwIDAgMjJjMS45NCAwIDQuNzUtMS4xNiA2LjExLTIuNTNsMi4zNi0yLjM2YTYuOTMgNi45MyAwIDAgMSA3LjU2LTEuMjJsLjg0LjQzYTguMDggOC4wOCAwIDAgMSA0LjEzIDYuNjZWMTQzaDM1LjAyeic+PC9wYXRoPjwvc3ZnPg==); +} + +.dark-mode { + --text-color: #eee; + --background-color: #333; + --background-color-rgb: 51, 51, 51; + --shadow-color: rgba(255, 255, 255, 0.2); + --border-color: rgb(131 131 131 / 50%); + --surface-color: rgba(51, 51, 51, 0.9); + --surface-solid: #292927; + --surface-muted: rgba(255, 255, 255, 0.055); + --text-muted: #b3b3ad; + --success-color: #58a86b; + --danger-color: #ff7168; + --warning-color: #e4ac4f; + --focus-ring: rgba(255, 204, 0, 0.26); + --background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxOTInIGhlaWdodD0nMTkyJyB2aWV3Qm94PScwIDAgMTkyIDE5Mic+PHBhdGggZmlsbD0nIzVhNWE1YScgZmlsbC1vcGFjaXR5PScwLjUnIGQ9J00xOTIgMTV2MmExMSAxMSAwIDAgMC0xMSAxMWMwIDEuOTQgMS4xNiA0Ljc1IDIuNTMgNi4xMWwyLjM2IDIuMzZhNi45MyA2LjkzIDAgMCAxIDEuMjIgNy41NmwtLjQzLjg0YTguMDggOC4wOCAwIDAgMS02LjY2IDQuMTNIMTQ1djM1LjAyYTYuMSA2LjEgMCAwIDAgMy4wMyA0Ljg3bC44NC40M2MxLjU4Ljc5IDQgLjQgNS4yNC0uODVsMi4zNi0yLjM2YTEyLjA0IDEyLjA0IDAgMCAxIDcuNTEtMy4xMSAxMyAxMyAwIDEgMSAuMDIgMjYgMTIgMTIgMCAwIDEtNy41My0zLjExbC0yLjM2LTIuMzZhNC45MyA0LjkzIDAgMCAwLTUuMjQtLjg1bC0uODQuNDNhNi4xIDYuMSAwIDAgMC0zLjAzIDQuODdWMTQzaDM1LjAyYTguMDggOC4wOCAwIDAgMSA2LjY2IDQuMTNsLjQzLjg0YTYuOTEgNi45MSAwIDAgMS0xLjIyIDcuNTZsLTIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxODEgMTY0YTExIDExIDAgMCAwIDExIDExdjJhMTMgMTMgMCAwIDEtMTMtMTMgMTIgMTIgMCAwIDEgMy4xMS03LjUzbDIuMzYtMi4zNmE0LjkzIDQuOTMgMCAwIDAgLjg1LTUuMjRsLS40My0uODRhNi4xIDYuMSAwIDAgMC00Ljg3LTMuMDNIMTQ1djM1LjAyYTguMDggOC4wOCAwIDAgMS00LjEzIDYuNjZsLS44NC40M2E2LjkxIDYuOTEgMCAwIDEtNy41Ni0xLjIybC0yLjM2LTIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgMTI0IDE4MWExMSAxMSAwIDAgMC0xMSAxMWgtMmExMyAxMyAwIDAgMSAxMy0xM2MyLjQ3IDAgNS43OSAxLjM3IDcuNTMgMy4xMWwyLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwIDUuMjQuODVsLjg0LS40M2E2LjEgNi4xIDAgMCAwIDMuMDMtNC44N1YxNDVoLTM1LjAyYTguMDggOC4wOCAwIDAgMS02LjY2LTQuMTNsLS40My0uODRhNi45MSA2LjkxIDAgMCAxIDEuMjItNy41NmwyLjM2LTIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgMTA3IDEyNGExMSAxMSAwIDAgMC0yMiAwYzAgMS45NCAxLjE2IDQuNzUgMi41MyA2LjExbDIuMzYgMi4zNmE2LjkzIDYuOTMgMCAwIDEgMS4yMiA3LjU2bC0uNDMuODRhOC4wOCA4LjA4IDAgMCAxLTYuNjYgNC4xM0g0OXYzNS4wMmE2LjEgNi4xIDAgMCAwIDMuMDMgNC44N2wuODQuNDNjMS41OC43OSA0IC40IDUuMjQtLjg1bDIuMzYtMi4zNmExMi4wNCAxMi4wNCAwIDAgMSA3LjUxLTMuMTFBMTMgMTMgMCAwIDEgODEgMTkyaC0yYTExIDExIDAgMCAwLTExLTExYy0xLjk0IDAtNC43NSAxLjE2LTYuMTEgMi41M2wtMi4zNiAyLjM2YTYuOTMgNi45MyAwIDAgMS03LjU2IDEuMjJsLS44NC0uNDNhOC4wOCA4LjA4IDAgMCAxLTQuMTMtNi42NlYxNDVIMTEuOThhNi4xIDYuMSAwIDAgMC00Ljg3IDMuMDNsLS40My44NGMtLjc5IDEuNTgtLjQgNCAuODUgNS4yNGwyLjM2IDIuMzZhMTIuMDQgMTIuMDQgMCAwIDEgMy4xMSA3LjUxQTEzIDEzIDAgMCAxIDAgMTc3di0yYTExIDExIDAgMCAwIDExLTExYzAtMS45NC0xLjE2LTQuNzUtMi41My02LjExbC0yLjM2LTIuMzZhNi45MyA2LjkzIDAgMCAxLTEuMjItNy41NmwuNDMtLjg0YTguMDggOC4wOCAwIDAgMSA2LjY2LTQuMTNINDd2LTM1LjAyYTYuMSA2LjEgMCAwIDAtMy4wMy00Ljg3bC0uODQtLjQzYy0xLjU5LS44LTQtLjQtNS4yNC44NWwtMi4zNiAyLjM2QTEyIDEyIDAgMCAxIDI4IDEwOWExMyAxMyAwIDEgMSAwLTI2YzIuNDcgMCA1Ljc5IDEuMzcgNy41MyAzLjExbDIuMzYgMi4zNmE0Ljk0IDQuOTQgMCAwIDAgNS4yNC44NWwuODQtLjQzQTYuMSA2LjEgMCAwIDAgNDcgODQuMDJWNDlIMTEuOThhOC4wOCA4LjA4IDAgMCAxLTYuNjYtNC4xM2wtLjQzLS44NGE2LjkxIDYuOTEgMCAwIDEgMS4yMi03LjU2bDIuMzYtMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxMSAyOCAxMSAxMSAwIDAgMCAwIDE3di0yYTEzIDEzIDAgMCAxIDEzIDEzYzAgMi40Ny0xLjM3IDUuNzktMy4xMSA3LjUzbC0yLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwLS44NSA1LjI0bC40My44NEE2LjEgNi4xIDAgMCAwIDExLjk4IDQ3SDQ3VjExLjk4YTguMDggOC4wOCAwIDAgMSA0LjEzLTYuNjZsLjg0LS40M2E2LjkxIDYuOTEgMCAwIDEgNy41NiAxLjIybDIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCA2OCAxMSAxMSAxMSAwIDAgMCA3OSAwaDJhMTMgMTMgMCAwIDEtMTMgMTMgMTIgMTIgMCAwIDEtNy41My0zLjExbC0yLjM2LTIuMzZhNC45MyA0LjkzIDAgMCAwLTUuMjQtLjg1bC0uODQuNDNBNi4xIDYuMSAwIDAgMCA0OSAxMS45OFY0N2gzNS4wMmE4LjA4IDguMDggMCAwIDEgNi42NiA0LjEzbC40My44NGE2LjkxIDYuOTEgMCAwIDEtMS4yMiA3LjU2bC0yLjM2IDIuMzZBMTAuMDYgMTAuMDYgMCAwIDAgODUgNjhhMTEgMTEgMCAwIDAgMjIgMGMwLTEuOTQtMS4xNi00Ljc1LTIuNTMtNi4xMWwtMi4zNi0yLjM2YTYuOTMgNi45MyAwIDAgMS0xLjIyLTcuNTZsLjQzLS44NGE4LjA4IDguMDggMCAwIDEgNi42Ni00LjEzSDE0M1YxMS45OGE2LjEgNi4xIDAgMCAwLTMuMDMtNC44N2wtLjg0LS40M2MtMS41OS0uOC00LS40LTUuMjQuODVsLTIuMzYgMi4zNkExMiAxMiAwIDAgMSAxMjQgMTNhMTMgMTMgMCAwIDEtMTMtMTNoMmExMSAxMSAwIDAgMCAxMSAxMWMxLjk0IDAgNC43NS0xLjE2IDYuMTEtMi41M2wyLjM2LTIuMzZhNi45MyA2LjkzIDAgMCAxIDcuNTYtMS4yMmwuODQuNDNhOC4wOCA4LjA4IDAgMCAxIDQuMTMgNi42NlY0N2gzNS4wMmE2LjEgNi4xIDAgMCAwIDQuODctMy4wM2wuNDMtLjg0Yy44LTEuNTkuNC00LS44NS01LjI0bC0yLjM2LTIuMzZBMTIgMTIgMCAwIDEgMTc5IDI4YTEzIDEzIDAgMCAxIDEzLTEzek04NC4wMiAxNDNhNi4xIDYuMSAwIDAgMCA0Ljg3LTMuMDNsLjQzLS44NGMuOC0xLjU5LjQtNC0uODUtNS4yNGwtMi4zNi0yLjM2QTEyIDEyIDAgMCAxIDgzIDEyNGExMyAxMyAwIDEgMSAyNiAwYzAgMi40Ny0xLjM3IDUuNzktMy4xMSA3LjUzbC0yLjM2IDIuMzZhNC45NCA0Ljk0IDAgMCAwLS44NSA1LjI0bC40My44NGE2LjEgNi4xIDAgMCAwIDQuODcgMy4wM0gxNDN2LTM1LjAyYTguMDggOC4wOCAwIDAgMSA0LjEzLTYuNjZsLjg0LS40M2E2LjkxIDYuOTEgMCAwIDEgNy41NiAxLjIybDIuMzYgMi4zNkExMC4wNiAxMC4wNiAwIDAgMCAxNjQgMTA3YTExIDExIDAgMCAwIDAtMjJjLTEuOTQgMC00Ljc1IDEuMTYtNi4xMSAyLjUzbC0yLjM2IDIuMzZhNi45MyA2LjkzIDAgMCAxLTcuNTYgMS4yMmwtLjg0LS40M2E4LjA4IDguMDggMCAwIDEtNC4xMy02LjY2VjQ5aC0zNS4wMmE2LjEgNi4xIDAgMCAwLTQuODcgMy4wM2wtLjQzLjg0Yy0uNzkgMS41OC0uNCA0IC44NSA1LjI0bDIuMzYgMi4zNmExMi4wNCAxMi4wNCAwIDAgMSAzLjExIDcuNTFBMTMgMTMgMCAxIDEgODMgNjhhMTIgMTIgMCAwIDEgMy4xMS03LjUzbDIuMzYtMi4zNmE0LjkzIDQuOTMgMCAwIDAgLjg1LTUuMjRsLS40My0uODRBNi4xIDYuMSAwIDAgMCA4NC4wMiA0OUg0OXYzNS4wMmE4LjA4IDguMDggMCAwIDEtNC4xMyA2LjY2bC0uODQuNDNhNi45MSA2LjkxIDAgMCAxLTcuNTYtMS4yMmwtMi4zNi0yLjM2QTEwLjA2IDEwLjA2IDAgMCAwIDI4IDg1YTExIDExIDAgMCAwIDAgMjJjMS45NCAwIDQuNzUtMS4xNiA2LjExLTIuNTNsMi4zNi0yLjM2YTYuOTMgNi45MyAwIDAgMSA3LjU2LTEuMjJsLjg0LjQzYTguMDggOC4wOCAwIDAgMSA0LjEzIDYuNjZWMTQzaDM1LjAyeic+PC9wYXRoPjwvc3ZnPg==); +} + +* { + box-sizing: border-box; +} + +html, +body, +#app { + min-height: 100%; + margin: 0; +} + +body { + min-width: 320px; + font-family: 'HarmonyOS_Regular', 'MiSans', 'HarmonyOS Sans SC', system-ui, sans-serif; + color: var(--text-color); + background: var(--background-color); + transition: color 0.2s ease, background-color 0.2s ease; +} + +button, +input, +textarea, +select { + font: inherit; +} + +button, +a { + -webkit-tap-highlight-color: transparent; +} + +button { + letter-spacing: 0; +} + +a { + color: inherit; + text-decoration: none; +} + +button:focus-visible, +a:focus-visible, +input:focus-visible, +textarea:focus-visible, +select:focus-visible { + outline: 3px solid var(--focus-ring); + outline-offset: 2px; +} + +ul { + padding: 0; + margin: 0; + list-style: none; +} + +.public-shell { + position: relative; + min-height: 100vh; + min-height: 100dvh; + display: flex; + flex-direction: column; + padding: 0 14px; + overflow-x: hidden; +} + +.background { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + z-index: -1; + background: var(--background-color) var(--background-image) repeat; +} + +.site-footer { + padding: 12px 0 16px; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + margin-top: auto; + font-size: 14px; + gap: 6px 12px; + color: var(--text-muted); + text-align: center; + + a { + color: inherit; + transition: color 0.2s ease; + &:hover { + color: var(--hover-link-color); + } + } +} + +.police-link { + display: flex; + align-items: center; + gap: 5px; +} + +@media screen and (max-width: 768px) { + .public-shell { + padding: 0 10px; + } + + .site-footer { + font-size: 12px; + padding-bottom: 10px; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +}