From 810bf469f98a32aec35f3308f184bde7aa9dba60 Mon Sep 17 00:00:00 2001 From: Ilia Miheev Date: Sun, 21 Jun 2026 00:36:31 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA?= =?UTF-8?q?=D0=B8=20=D1=82=D0=B5=D0=BC=D1=8B=20=D0=BF=D0=BE=20=D1=83=D0=BC?= =?UTF-8?q?=D0=BE=D0=BB=D1=87=D0=B0=D0=BD=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 5 ++++- Readme.md | 2 ++ docker-compose.yml | 1 + docker/entrypoint.sh | 4 +++- src/App.vue | 4 ++-- src/assets/style.css | 24 +++++++++++++----------- src/composables/useTheme.js | 17 ++++------------- src/config/applyTheme.js | 12 ++++++++++++ src/config/defaults.js | 6 ++++++ src/config/loadConfig.js | 5 ++++- 10 files changed, 51 insertions(+), 29 deletions(-) diff --git a/.env.example b/.env.example index 32dfd32..f6c0f46 100644 --- a/.env.example +++ b/.env.example @@ -7,4 +7,7 @@ VITE_BANNER_SLOGAN=— qr-код без излишеств VITE_BANNER_URL=https://github.com/IliaMiheev/iQR # Акцентный цвет (hex, rgb или css-имя) -VITE_ACCENT_COLOR=#ff4500 +VITE_ACCENT_COLOR="#ff4500" + +# Тема по умолчанию: dark или light (если пользователь ещё не переключал) +VITE_DEFAULT_THEME=light diff --git a/Readme.md b/Readme.md index fe2b7df..0c235b1 100644 --- a/Readme.md +++ b/Readme.md @@ -26,6 +26,7 @@ npm run dev | `VITE_BANNER_SLOGAN` | Слоган в плашке | `— qr-код без излишеств` | | `VITE_BANNER_URL` | Ссылка плашки | репозиторий iQR | | `VITE_ACCENT_COLOR` | Акцентный цвет | `#ff4500` | +| `VITE_DEFAULT_THEME` | Тема по умолчанию (`dark` / `light`) | `dark` | После изменения `.env` перезапустите `npm run dev`. @@ -46,6 +47,7 @@ environment: BANNER_SLOGAN: "— быстрые qr-коды" BANNER_URL: "https://example.com" ACCENT_COLOR: "#7c3aed" + DEFAULT_THEME: "light" ``` При старте контейнера генерируется `config.json`, поэтому пересборка образа для смены текстов не нужна. diff --git a/docker-compose.yml b/docker-compose.yml index 48626d0..31def30 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,4 +9,5 @@ services: BANNER_SLOGAN: "— qr-код без излишеств" BANNER_URL: "https://github.com/IliaMiheev/iQR" ACCENT_COLOR: "#ff4500" + DEFAULT_THEME: "dark" restart: unless-stopped diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 67a66bc..c8cdee2 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -6,6 +6,7 @@ BANNER_BRAND="${BANNER_BRAND:-iQR}" BANNER_SLOGAN="${BANNER_SLOGAN:-— qr-код без излишеств}" BANNER_URL="${BANNER_URL:-https://github.com/IliaMiheev/iQR}" ACCENT_COLOR="${ACCENT_COLOR:-#ff4500}" +DEFAULT_THEME="${DEFAULT_THEME:-dark}" cat > /usr/share/nginx/html/config.json < /usr/share/nginx/html/config.json < {
diff --git a/src/assets/style.css b/src/assets/style.css index dd78d0e..46e9586 100644 --- a/src/assets/style.css +++ b/src/assets/style.css @@ -474,8 +474,9 @@ html.light .qr-modal__backdrop { border: 1px solid var(--surface-border); border-radius: 50%; background: var(--surface-solid); - color: var(--text); + color: var(--accent); font-size: 22px; + font-weight: 700; line-height: 1; display: flex; align-items: center; @@ -486,7 +487,8 @@ html.light .qr-modal__backdrop { } .qr-modal__close:hover { - border-color: var(--accent); + background: var(--surface-hover); + border-color: var(--surface-border); color: var(--accent); transform: scale(1.05); } @@ -511,7 +513,7 @@ html.light .qr-modal__backdrop { padding: 6px; border-radius: 12px; background: var(--surface-solid); - border: 1px solid var(--surface-border); + border: none; box-shadow: var(--shadow-sm); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); @@ -531,11 +533,18 @@ html.light .qr-modal__backdrop { border: 1px solid transparent; border-radius: 8px; box-shadow: none; + transition: + color 0.2s ease, + border-color 0.2s ease, + background-color 0.2s ease, + box-shadow 0.2s ease, + transform 0.15s ease; } .qr-modal__toolbar button:hover { background: var(--surface-hover); - border-color: var(--surface-border); + color: var(--accent); + border-color: var(--accent); transform: none; box-shadow: none; } @@ -551,11 +560,6 @@ html.light .qr-modal__backdrop { justify-content: center; } -.qr-modal__tool-btn:hover { - color: var(--accent); - border-color: var(--accent) !important; -} - .qr-modal__status-slot { position: relative; display: flex; @@ -617,7 +621,6 @@ html.light .qr-modal__backdrop { #qr-code { display: inline-flex; - padding: 16px; border-radius: 16px; background: var(--surface-solid); border: 1px solid var(--surface-border); @@ -643,7 +646,6 @@ canvas { display: block; border-radius: 8px; background: var(--canvas-bg); - border: 1px solid var(--canvas-border); image-rendering: pixelated; transition: border-color 0.35s ease; } diff --git a/src/composables/useTheme.js b/src/composables/useTheme.js index 655bb44..0f35afb 100644 --- a/src/composables/useTheme.js +++ b/src/composables/useTheme.js @@ -1,7 +1,9 @@ -import { ref, computed, onMounted } from 'vue' +import { inject, ref, computed } from 'vue' +import { resolveTheme } from '../config/applyTheme' export function useTheme() { - const theme = ref('dark') + const config = inject('appConfig') + const theme = ref(resolveTheme(config)) const isLightTheme = computed(() => theme.value === 'light') @@ -15,16 +17,5 @@ export function useTheme() { applyTheme(theme.value === 'dark' ? 'light' : 'dark') } - onMounted(() => { - const saved = localStorage.getItem('theme') - if (saved === 'dark' || saved === 'light') { - applyTheme(saved) - return - } - - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches - applyTheme(prefersDark ? 'dark' : 'light') - }) - return { theme, isLightTheme, toggleTheme } } diff --git a/src/config/applyTheme.js b/src/config/applyTheme.js index 54cb9ff..80fc119 100644 --- a/src/config/applyTheme.js +++ b/src/config/applyTheme.js @@ -1,7 +1,19 @@ +import { normalizeTheme } from './defaults' + +export function resolveTheme(config) { + const saved = localStorage.getItem('theme') + if (saved === 'dark' || saved === 'light') { + return saved + } + + return normalizeTheme(config.defaultTheme) +} + export function applyAppConfig(config) { const root = document.documentElement root.style.setProperty('--accent', config.accentColor) + root.className = resolveTheme(config) const themeMeta = document.querySelector('meta[name="theme-color"]') if (themeMeta) { diff --git a/src/config/defaults.js b/src/config/defaults.js index 66143f5..ed219c0 100644 --- a/src/config/defaults.js +++ b/src/config/defaults.js @@ -4,4 +4,10 @@ export const defaultConfig = { bannerSlogan: '— qr-код без излишеств', bannerUrl: 'https://github.com/IliaMiheev/iQR', accentColor: '#ff4500', + defaultTheme: 'dark', +} + +export function normalizeTheme(value) { + const normalized = String(value ?? '').trim().toLowerCase() + return normalized === 'light' ? 'light' : 'dark' } diff --git a/src/config/loadConfig.js b/src/config/loadConfig.js index f9c6896..48a099a 100644 --- a/src/config/loadConfig.js +++ b/src/config/loadConfig.js @@ -1,4 +1,4 @@ -import { defaultConfig } from './defaults' +import { defaultConfig, normalizeTheme } from './defaults' function fromBuildEnv() { const env = import.meta.env @@ -9,6 +9,7 @@ function fromBuildEnv() { bannerSlogan: env.VITE_BANNER_SLOGAN, bannerUrl: env.VITE_BANNER_URL, accentColor: env.VITE_ACCENT_COLOR, + defaultTheme: env.VITE_DEFAULT_THEME, }) } @@ -44,5 +45,7 @@ export async function loadConfig() { } } + config.defaultTheme = normalizeTheme(config.defaultTheme) + return config }