diff --git a/devkit_web.py b/devkit_web.py index 269fc19..f835b9c 100644 --- a/devkit_web.py +++ b/devkit_web.py @@ -241,8 +241,12 @@ async def page_save_ui_preferences(self): if appearance_mode not in {"auto", "light", "dark"}: appearance_mode = "auto" prefs = self._read_ui_preferences() + previous_appearance_mode = str(data.get("previous_appearance_mode", prefs.get("previous_appearance_mode", "auto"))).strip().lower() + if previous_appearance_mode not in {"auto", "light", "dark"}: + previous_appearance_mode = "auto" prefs["palette_mode"] = palette_mode prefs["appearance_mode"] = appearance_mode + prefs["previous_appearance_mode"] = previous_appearance_mode prefs["updated_at"] = int(time.time()) self._write_ui_preferences(prefs) return self._jsonify({"ok": True, "preferences": prefs}) @@ -398,6 +402,8 @@ def _read_ui_preferences(self) -> dict[str, Any]: data["palette_mode"] = palette_mode if palette_mode in {"luxury", "bluewhite", "vivid", "void"} else "luxury" appearance_mode = str(data.get("appearance_mode", "auto")).strip().lower() data["appearance_mode"] = appearance_mode if appearance_mode in {"auto", "light", "dark"} else "auto" + previous_appearance_mode = str(data.get("previous_appearance_mode", data["appearance_mode"])).strip().lower() + data["previous_appearance_mode"] = previous_appearance_mode if previous_appearance_mode in {"auto", "light", "dark"} else data["appearance_mode"] return data except Exception: logger.warning("ui_preferences.json 读取失败,已使用默认偏好") diff --git a/pages/settings/app.js b/pages/settings/app.js index 9e0bf61..6aaa206 100644 --- a/pages/settings/app.js +++ b/pages/settings/app.js @@ -7,8 +7,13 @@ const PALETTE_MODES = ["luxury", "bluewhite", "vivid", "void"]; const APPEARANCE_MODES = ["auto", "dark", "light"]; const PALETTE_LABELS = { luxury: "鎏金", bluewhite: "冰蓝", vivid: "霓虹", void: "暗涌" }; const APPEARANCE_LABELS = { auto: "自动", dark: "深色", light: "浅色" }; +const BACKGROUND_MODES = ["preset", "custom"]; +const CUSTOM_BACKGROUND_SIZE = { width: 1920, height: 1080, quality: 0.86 }; let paletteMode = "luxury"; let appearanceMode = "auto"; +let previousAppearanceMode = "auto"; +let backgroundMode = "preset"; +let customBackgroundUrl = ""; let api = null; let toolGroupsDef = {}; let groupsData = []; @@ -89,11 +94,45 @@ function getStoredPaletteMode() { return paletteMode; } +function refreshThemeControls() { + const custom = backgroundMode === "custom"; + const hasCustomBackground = Boolean(customBackgroundUrl); + const backgroundLabel = document.getElementById("backgroundModeLabel"); + if (backgroundLabel) backgroundLabel.textContent = custom ? "上传" : (hasCustomBackground ? "自定义" : "上传"); + document.getElementById("presetBackgroundBtn")?.toggleAttribute("disabled", !custom); + const appearanceBtn = document.getElementById("appearanceToggleBtn"); + if (appearanceBtn) { + appearanceBtn.toggleAttribute("disabled", custom); + appearanceBtn.title = custom ? "自定义背景下已锁定深色模式" : "切换明暗模式"; + } + const appearanceLabel = document.getElementById("appearanceModeLabel"); + if (custom && appearanceLabel) appearanceLabel.textContent = "锁定"; + document.documentElement.dataset.backgroundMode = custom ? "custom" : "preset"; +} + +function applyCustomBackground(url) { + customBackgroundUrl = String(url || ""); + if (customBackgroundUrl) { + if (backgroundMode !== "custom") previousAppearanceMode = APPEARANCE_MODES.includes(appearanceMode) ? appearanceMode : getStoredAppearanceMode(); + backgroundMode = "custom"; + appearanceMode = "dark"; + document.documentElement.style.setProperty("--custom-bg-image", `url("${customBackgroundUrl}")`); + applyPalette(paletteMode); + applyAppearance("dark"); + } else { + backgroundMode = "preset"; + document.documentElement.style.removeProperty("--custom-bg-image"); + applyPalette(paletteMode); + applyAppearance(appearanceMode); + } +} + function applyPalette(mode = getStoredPaletteMode()) { paletteMode = PALETTE_MODES.includes(mode) ? mode : "luxury"; document.documentElement.dataset.palette = paletteMode; const label = document.getElementById("paletteModeLabel"); if (label) label.textContent = PALETTE_LABELS[paletteMode] || "鎏金"; + refreshThemeControls(); } function getStoredAppearanceMode() { @@ -109,14 +148,21 @@ function resolveAppearance(mode) { } function applyAppearance(mode = getStoredAppearanceMode()) { + if (backgroundMode === "custom") mode = "dark"; appearanceMode = APPEARANCE_MODES.includes(mode) ? mode : "auto"; document.documentElement.dataset.appearance = appearanceMode; - document.documentElement.dataset.theme = resolveAppearance(appearanceMode); + document.documentElement.dataset.theme = backgroundMode === "custom" ? "dark" : resolveAppearance(appearanceMode); const label = document.getElementById("appearanceModeLabel"); - if (label) label.textContent = APPEARANCE_LABELS[appearanceMode] || "自动"; + if (label) label.textContent = backgroundMode === "custom" ? "锁定" : (APPEARANCE_LABELS[appearanceMode] || "自动"); + refreshThemeControls(); } async function cycleAppearanceMode() { + if (backgroundMode === "custom") { + applyAppearance("dark"); + showToast("自定义背景下已锁定深色模式"); + return; + } const current = appearanceMode || getStoredAppearanceMode(); const currentIndex = APPEARANCE_MODES.includes(current) ? APPEARANCE_MODES.indexOf(current) : 0; const next = APPEARANCE_MODES[(currentIndex + 1) % APPEARANCE_MODES.length]; @@ -141,28 +187,160 @@ async function loadUiPreferences() { const data = await api.safeGet("ui_preferences"); const palette = data.preferences?.palette_mode; const appearance = data.preferences?.appearance_mode; + const previousAppearance = data.preferences?.previous_appearance_mode; + const bgMode = data.preferences?.background_mode; + const bgUrl = data.preferences?.custom_background_url; + customBackgroundUrl = String(bgUrl || ""); + if (BACKGROUND_MODES.includes(bgMode)) backgroundMode = bgMode; + if (APPEARANCE_MODES.includes(previousAppearance)) previousAppearanceMode = previousAppearance; if (APPEARANCE_MODES.includes(appearance)) { appearanceMode = appearance; saveAppearanceLocally(appearance); - applyAppearance(appearance); } if (PALETTE_MODES.includes(palette)) { paletteMode = palette; savePaletteLocally(palette); - applyPalette(palette); + } + if (backgroundMode === "custom" && bgUrl) { + applyCustomBackground(bgUrl); return; } + backgroundMode = "preset"; + applyPalette(paletteMode); + applyAppearance(appearanceMode); + return; } catch (e) { console.warn("loadUiPreferences", e); } + backgroundMode = "preset"; applyPalette(); applyAppearance(); } async function saveUiPreferences() { if (!api) return; - try { await api.safePost("ui_preferences/save", { palette_mode: paletteMode, appearance_mode: appearanceMode }); } + try { + await api.safePost("ui_preferences/save", { + palette_mode: paletteMode, + appearance_mode: appearanceMode, + previous_appearance_mode: previousAppearanceMode, + background_mode: backgroundMode, + custom_background_url: customBackgroundUrl || "", + }); + } catch (e) { console.warn("saveUiPreferences", e); } } +function activateStoredCustomBackground() { + if (!customBackgroundUrl) return false; + applyCustomBackground(customBackgroundUrl); + saveUiPreferences(); + showToast("已启用上次自定义背景"); + return true; +} + +function handleBackgroundButtonClick() { + if (backgroundMode === "custom") { + document.getElementById("customBackgroundInput")?.click(); + return; + } + if (activateStoredCustomBackground()) return; + document.getElementById("customBackgroundInput")?.click(); +} + +async function switchToPresetBackground() { + backgroundMode = "preset"; + const restoredAppearance = APPEARANCE_MODES.includes(previousAppearanceMode) ? previousAppearanceMode : "auto"; + appearanceMode = restoredAppearance; + saveAppearanceLocally(restoredAppearance); + document.documentElement.style.removeProperty("--custom-bg-image"); + applyPalette(paletteMode); + applyAppearance(restoredAppearance); + await saveUiPreferences(); + showToast("已切换到预设配色,自定义背景已保留"); +} + +function loadImageFromDataUrl(dataUrl) { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = () => reject(new Error("图片加载失败")); + image.src = dataUrl; + }); +} + +function fileToDataUrl(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(String(reader.result || "")); + reader.onerror = () => reject(new Error("图片读取失败")); + reader.readAsDataURL(file); + }); +} + +async function cropBackgroundFile(file) { + if (!file?.type?.startsWith("image/")) throw new Error("请选择图片文件"); + if (file.size > 12 * 1024 * 1024) throw new Error("图片不能超过 12MB"); + const dataUrl = await fileToDataUrl(file); + const image = await loadImageFromDataUrl(dataUrl); + const { width, height, quality } = CUSTOM_BACKGROUND_SIZE; + const targetRatio = width / height; + const sourceRatio = image.width / image.height; + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext("2d"); + + // 第一层:用原图 cover 模式模糊铺满,避免留黑边。 + let coverSx = 0, coverSy = 0, coverSw = image.width, coverSh = image.height; + if (sourceRatio > targetRatio) { + coverSw = image.height * targetRatio; + coverSx = (image.width - coverSw) / 2; + } else { + coverSh = image.width / targetRatio; + coverSy = (image.height - coverSh) / 2; + } + ctx.save(); + ctx.filter = "blur(28px) saturate(1.08) brightness(0.82)"; + ctx.drawImage(image, coverSx, coverSy, coverSw, coverSh, -36, -36, width + 72, height + 72); + ctx.restore(); + + // 第二层:用 contain 模式尽量保留完整图片主体。 + let drawW = width; + let drawH = height; + if (sourceRatio > targetRatio) { + drawH = width / sourceRatio; + } else { + drawW = height * sourceRatio; + } + const dx = (width - drawW) / 2; + const dy = (height - drawH) / 2; + + // 给主体图加轻微暗色描边/阴影,提升浅色和深色模式下的可读性。 + ctx.save(); + ctx.shadowColor = "rgba(0,0,0,.34)"; + ctx.shadowBlur = 34; + ctx.shadowOffsetY = 8; + ctx.drawImage(image, dx, dy, drawW, drawH); + ctx.restore(); + + return canvas.toDataURL("image/jpeg", quality); +} + +async function handleCustomBackgroundUpload(event) { + const file = event.currentTarget.files?.[0]; + event.currentTarget.value = ""; + if (!file) return; + try { + showToast("正在裁切并保存背景图..."); + const url = await cropBackgroundFile(file); + applyCustomBackground(url); + await saveUiPreferences(); + showToast("自定义背景已启用(深色模式)"); + } catch (e) { + console.error("handleCustomBackgroundUpload", e); + showToast(e.message || "背景图处理失败"); + } +} + async function init() { applyPalette(); applyAppearance(); @@ -171,6 +349,9 @@ async function init() { }); document.getElementById("paletteToggleBtn")?.addEventListener("click", cyclePaletteMode); document.getElementById("appearanceToggleBtn")?.addEventListener("click", cycleAppearanceMode); + document.getElementById("customBackgroundBtn")?.addEventListener("click", handleBackgroundButtonClick); + document.getElementById("customBackgroundInput")?.addEventListener("change", handleCustomBackgroundUpload); + document.getElementById("presetBackgroundBtn")?.addEventListener("click", switchToPresetBackground); document.getElementById("refreshGroupsBtn")?.addEventListener("click", async () => { await loadContacts(); showToast("群聊/私聊列表已刷新"); @@ -410,17 +591,13 @@ function renderToolGroupCards() { const rows = tools.map(tool => { const checked = !disabled.has(tool.id); return ` - + ${emojiForName(tool.name || tool.id)} ${escapeHtml(tool.name)} ${escapeHtml(tool.desc)} - - - - - `; + `; }).join(""); const card = ` @@ -449,17 +626,18 @@ function bindConfigEvents() { input.addEventListener("change", event => { const groupName = event.currentTarget.dataset.group; currentConfig.tool_groups[groupName] = event.currentTarget.checked; - document.querySelectorAll(`.tool-toggle[data-group-name="${CSS.escape(groupName)}"]`).forEach(toolToggle => { - toolToggle.checked = event.currentTarget.checked; - setToolDisabled(toolToggle.dataset.tool, !event.currentTarget.checked); + asToolItems(toolGroupsDef[groupName] || []).forEach(tool => { + setToolDisabled(tool.id, !event.currentTarget.checked); }); renderConfigPanel(); }); }); - document.querySelectorAll(".tool-toggle").forEach(input => { - input.addEventListener("change", event => { - setToolDisabled(event.currentTarget.dataset.tool, !event.currentTarget.checked); + document.querySelectorAll(".tool-action").forEach(button => { + button.addEventListener("click", event => { + const toolId = event.currentTarget.dataset.tool; + const disabledSet = new Set(currentConfig.disabled_tools || []); + setToolDisabled(toolId, !disabledSet.has(toolId)); renderConfigPanel(); }); }); diff --git a/pages/settings/styles.css b/pages/settings/styles.css index bb9dd3b..5024898 100644 --- a/pages/settings/styles.css +++ b/pages/settings/styles.css @@ -131,13 +131,14 @@ body { } button, input { font: inherit; } button { cursor: pointer; } +button:disabled { cursor: not-allowed; opacity: .46; filter: grayscale(.35); transform: none !important; } .noise { position: fixed; inset: 0; pointer-events: none; opacity: .12; background-image: linear-gradient(transparent 50%, rgba(255,255,255,.05) 50%); background-size: 100% 4px; mix-blend-mode: overlay; } .side-rail { position: relative; z-index: 1; height: 100vh; padding: 14px 0 14px 14px; border-right: 1px solid var(--line); background: linear-gradient(180deg, var(--panel), rgba(0,0,0,.18)); backdrop-filter: blur(26px) saturate(1.25); display: flex; flex-direction: column; gap: 9px; } .brand-card { display: flex; gap: 9px; align-items: center; padding: 10px 12px; margin-right: 12px; border: 1px solid var(--line); border-radius: 18px; background: var(--panel-2); box-shadow: var(--shadow); } .brand-mark { width: 38px; height: 38px; border-radius: 13px; display: block; object-fit: cover; object-position: center; background: rgba(0,0,0,.35); border: 1px solid var(--line); box-shadow: 0 18px 44px rgba(0,0,0,.32); } .brand-card h1 { margin: 0; font-size: 15px; letter-spacing: -.03em; line-height: 1.12; } .brand-card p { margin: 3px 0 0; color: var(--muted); font-size: 11px; } -.palette-dock { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-right: 12px; } +.palette-dock { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; margin-right: 12px; } .dock-button { display: grid; justify-items: center; gap: 2px; border: 1px solid var(--line); color: var(--text); background: var(--panel-2); border-radius: 13px; padding: 7px 6px; font-weight: 850; } .dock-button b { color: var(--accent-3); font-size: 11px; line-height: 1.1; } .dock-button:hover { transform: translateY(-1px); border-color: var(--accent); } @@ -253,3 +254,266 @@ button { cursor: pointer; } .workspace::-webkit-scrollbar { width: 6px; } .group-list::-webkit-scrollbar-thumb, .workspace::-webkit-scrollbar-thumb { background: var(--gradient); border-radius: 999px; } @media (max-width: 1040px) { body { grid-template-columns: 1fr; overflow: auto; } .side-rail { height: auto; max-height: 48vh; } .workspace { height: auto; min-height: 60vh; } .hero-grid, .top-pair, .board { grid-template-columns: 1fr; } } + +/* custom background glass layer */ +:root[data-background-mode="custom"][data-theme="dark"] { + --panel: rgba(8, 10, 18, .42); + --panel-2: rgba(255,255,255,.10); + --card: rgba(255,255,255,.13); + --card-dark: rgba(8,10,18,.36); + --text: #fff; + --text-invert: #fff; + --muted: rgba(255,255,255,.78); + --line: rgba(255,255,255,.34); + --shadow: 0 10px 28px rgba(0,0,0,.16); + color-scheme: dark; +} +:root[data-background-mode="custom"][data-theme="light"] { + --panel: rgba(255,255,255,.46); + --panel-2: rgba(255,255,255,.24); + --card: rgba(255,255,255,.34); + --card-dark: rgba(255,255,255,.30); + --text: #151923; + --text-invert: #151923; + --muted: rgba(21,25,35,.76); + --line: rgba(255,255,255,.34); + --shadow: 0 10px 28px rgba(37,49,74,.12); + color-scheme: light; +} +:root[data-background-mode="custom"][data-theme="dark"] body { + background: linear-gradient(135deg, rgba(0,0,0,.30), rgba(0,0,0,.42)), var(--custom-bg-image) center / cover fixed no-repeat !important; +} +:root[data-background-mode="custom"][data-theme="light"] body { + background: linear-gradient(135deg, rgba(255,255,255,.38), rgba(242,246,255,.48)), var(--custom-bg-image) center / cover fixed no-repeat !important; +} +:root[data-background-mode="custom"] .noise { + opacity: .035 !important; + display: block !important; +} +:root[data-background-mode="custom"] .hero-card, +:root[data-background-mode="custom"] .card, +:root[data-background-mode="custom"] .path-card, +:root[data-background-mode="custom"] .group-card { + background: rgba(255,255,255,.006) !important; + border-color: rgba(255,255,255,.32) !important; + box-shadow: none !important; + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; +} +:root[data-background-mode="custom"] .brand-card, +:root[data-background-mode="custom"] .dock-button, +:root[data-background-mode="custom"] .side-meta div, +:root[data-background-mode="custom"] .global-contact-card, +:root[data-background-mode="custom"] .contact-section-head, +:root[data-background-mode="custom"] .group-item, +:root[data-background-mode="custom"] .small-stat, +:root[data-background-mode="custom"] .bulk-row, +:root[data-background-mode="custom"] .path-grid label, +:root[data-background-mode="custom"] .input-field, +:root[data-background-mode="custom"] textarea, +:root[data-background-mode="custom"] select, +:root[data-background-mode="custom"] .tool-card, +:root[data-background-mode="custom"] .tool-card.tool-action, +:root[data-background-mode="custom"] .tool-card.tool-action.enabled, +:root[data-background-mode="custom"] .tool-card.tool-action.disabled { + background: rgba(255,255,255,.006) !important; + border-color: rgba(255,255,255,.28) !important; + box-shadow: none !important; + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; + opacity: 1 !important; + filter: none !important; +} +:root[data-background-mode="custom"] h1, +:root[data-background-mode="custom"] h2, +:root[data-background-mode="custom"] h3, +:root[data-background-mode="custom"] b, +:root[data-background-mode="custom"] .group-name, +:root[data-background-mode="custom"] .tool-name, +:root[data-background-mode="custom"] .field-label, +:root[data-background-mode="custom"] .section-title, +:root[data-background-mode="custom"] .confirm-title { + text-shadow: 0 2px 10px rgba(0,0,0,.42); +} +:root[data-background-mode="custom"] .input-field::placeholder { color: rgba(255,255,255,.56) !important; } + +/* optional path panel */ +:root[data-background-mode="custom"] .path-card { + min-height: 0 !important; + overflow: visible !important; +} +:root[data-background-mode="custom"] .path-card::before, +:root[data-background-mode="custom"] .path-card::after { + content: none !important; + display: none !important; +} +:root[data-background-mode="custom"] .path-head, +:root[data-background-mode="custom"] .path-grid, +:root[data-background-mode="custom"] .path-grid label, +:root[data-background-mode="custom"] .path-grid .input-field { + position: relative !important; + z-index: 2 !important; +} +:root[data-background-mode="custom"] .path-grid { + display: grid !important; + grid-template-columns: repeat(2, minmax(0, 1fr)) !important; + gap: 10px 12px !important; + align-items: start !important; +} +:root[data-background-mode="custom"] .path-grid label { + display: grid !important; + grid-template-rows: auto 1fr !important; + gap: 3px !important; + min-width: 0 !important; + min-height: 58px !important; + padding: 8px 13px !important; + border-radius: 999px !important; + overflow: hidden !important; +} +:root[data-background-mode="custom"] .path-grid label:nth-child(3) { grid-column: 1 / 2 !important; } +:root[data-background-mode="custom"] .path-grid label > span { + padding: 0 3px !important; + font-size: 11px !important; + line-height: 1.1 !important; + color: var(--muted) !important; + opacity: .82 !important; +} +:root[data-background-mode="custom"] .path-grid .input-field, +:root[data-background-mode="custom"] .path-input, +:root[data-background-mode="custom"] .path-input:focus { + width: 100% !important; + min-height: 22px !important; + min-width: 0 !important; + padding: 0 3px !important; + margin: 0 !important; + box-sizing: border-box !important; + border: 0 !important; + border-radius: 0 !important; + background: transparent !important; + box-shadow: none !important; + outline: none !important; + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; +} +:root[data-background-mode="custom"] .path-grid label:focus-within { + border-color: rgba(255,255,255,.28) !important; + background: transparent !important; + box-shadow: none !important; +} + +/* click-to-toggle tool rows */ +.tool-card.tool-action { + width: 100%; + cursor: pointer; + text-align: left; + grid-template-columns: 34px 1fr !important; + appearance: none; + -webkit-appearance: none; + color: inherit; + border-width: 2px !important; +} +:root[data-background-mode="custom"] .group-item.active, +:root[data-background-mode="custom"] .global-contact-card:has(.group-item.active), +:root[data-background-mode="custom"] .tool-card.tool-action.enabled { + border-color: var(--accent) !important; + background: rgba(255,255,255,.024) !important; + box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 0 0 1px color-mix(in srgb, var(--accent) 36%, transparent) inset !important; +} +.tool-card.tool-action.enabled .tool-emoji, +.tool-card.tool-action.disabled .tool-emoji { + background: rgba(255,255,255,.12) !important; + opacity: 1 !important; + filter: none !important; +} +.tool-card.tool-action.enabled .tool-emoji { border-color: var(--accent) !important; } +:root[data-background-mode="custom"] .tool-card.tool-action:focus-visible { + outline: 2px solid var(--accent) !important; + outline-offset: 2px !important; +} + +/* themed group switch */ +.group-head { align-items: center !important; } +.group-head > .switch { + margin-left: auto !important; + align-self: center !important; + justify-self: end !important; +} +.group-head .switch input:checked + .switch-track, +:root[data-background-mode="custom"] .group-head .switch input:checked + .switch-track { + background: var(--gradient) !important; + border-color: var(--accent) !important; +} +.group-head .switch input:not(:checked) + .switch-track, +:root[data-background-mode="custom"] .group-head .switch input:not(:checked) + .switch-track { + background: rgba(255,255,255,.10) !important; + border-color: rgba(255,255,255,.24) !important; +} +.group-head .switch-thumb { + background: #fff !important; + box-shadow: 0 2px 8px rgba(0,0,0,.24) !important; +} + +/* performance: reduce repaint cost during scrolling */ +:root[data-background-mode="custom"] .workspace, +:root[data-background-mode="custom"] .group-list { + transform: translateZ(0); + will-change: scroll-position; + contain: layout paint style; +} +:root[data-background-mode="custom"] .tool-card, +:root[data-background-mode="custom"] .group-item, +:root[data-background-mode="custom"] .bulk-row, +:root[data-background-mode="custom"] .path-grid label, +:root[data-background-mode="custom"] .input-field, +:root[data-background-mode="custom"] .tool-emoji, +:root[data-background-mode="custom"] .group-icon, +:root[data-background-mode="custom"] .switch-track, +:root[data-background-mode="custom"] .dock-button { + transition: border-color .12s ease, background-color .12s ease !important; +} +:root[data-background-mode="custom"] .brand-card::before, +:root[data-background-mode="custom"] .brand-card::after, +:root[data-background-mode="custom"] .dock-button::before, +:root[data-background-mode="custom"] .dock-button::after, +:root[data-background-mode="custom"] .group-item::before, +:root[data-background-mode="custom"] .group-item::after, +:root[data-background-mode="custom"] .hero-card::before, +:root[data-background-mode="custom"] .hero-card::after, +:root[data-background-mode="custom"] .card::before, +:root[data-background-mode="custom"] .card::after, +:root[data-background-mode="custom"] .path-card::before, +:root[data-background-mode="custom"] .path-card::after, +:root[data-background-mode="custom"] .group-card::before, +:root[data-background-mode="custom"] .group-card::after, +:root[data-background-mode="custom"] .tool-card::before, +:root[data-background-mode="custom"] .tool-card::after { + content: none !important; + display: none !important; +} +:root[data-background-mode="custom"] .tool-card:hover, +:root[data-background-mode="custom"] .group-item:hover, +:root[data-background-mode="custom"] .dock-button:hover, +:root[data-background-mode="custom"] .bulk-row:hover, +:root[data-background-mode="custom"] .card:hover, +:root[data-background-mode="custom"] .group-card:hover { + transform: none !important; + filter: none !important; +} +@media (prefers-reduced-motion: reduce) { + :root[data-background-mode="custom"] .hero-card, + :root[data-background-mode="custom"] .card, + :root[data-background-mode="custom"] .path-card, + :root[data-background-mode="custom"] .group-card, + :root[data-background-mode="custom"] .brand-card, + :root[data-background-mode="custom"] .dock-button, + :root[data-background-mode="custom"] .global-contact-card, + :root[data-background-mode="custom"] .contact-section-head, + :root[data-background-mode="custom"] .group-item, + :root[data-background-mode="custom"] .small-stat, + :root[data-background-mode="custom"] .bulk-row, + :root[data-background-mode="custom"] .path-grid label, + :root[data-background-mode="custom"] .tool-card { + animation: none !important; + transition: none !important; + } +}