diff --git a/index.html b/index.html index 2819962..5790a71 100644 --- a/index.html +++ b/index.html @@ -28,6 +28,7 @@ + diff --git a/locales/en.js b/locales/en.js index a6e6e61..20a05ec 100644 --- a/locales/en.js +++ b/locales/en.js @@ -164,6 +164,13 @@ const en = { // Wallpaper and settings "uploadWallpaperText": "Upload Wallpaper", // Keep this short "rangColor": "Pick color", // Keep this short + + // Glassmorphism + "glassmorphismText": "Glassmorphism", + "glassmorphismInfo": "Enable frosted glass (backdrop blur)", + "glassBlurTitle": "Blur Intensity", + "glassBlurDesc": "Adjust backdrop blur", + "opacityTitle": "Opacity", "adjustOpacityDesc": "Adjust interface transparency", "backupText": "Backup", diff --git a/scripts/glassmorphism.js b/scripts/glassmorphism.js new file mode 100644 index 0000000..d014c06 --- /dev/null +++ b/scripts/glassmorphism.js @@ -0,0 +1,150 @@ +/* + * Glassmorphism feature + * Adds optional backdrop blur and supports a toggle + blur intensity slider + */ + +/* ======================= + DOM ELEMENTS +======================= */ + +const glassCheckbox = document.getElementById("glassCheckbox"); +const glassBlurControl = document.getElementById("glassBlurControl"); +const glassBar = document.querySelector("#glassBlurControl .opacityBar"); +const glassSlider = document.getElementById("glassSlider"); +const glassBlurLevel = document.getElementById("glassBlurLevel"); + +/* ======================= + CONFIG +======================= */ + +const MAX_BLUR = + parseInt( + getComputedStyle(document.documentElement) + .getPropertyValue("--glass-max-blur") + ) || 30; + +/* ======================= + CORE FUNCTIONS +======================= */ + +function setGlassBlur(px) { + if (!glassSlider || !glassBlurLevel) return; + + const clamped = Math.max(0, Math.min(MAX_BLUR, Math.round(px))); + const percent = (clamped / MAX_BLUR) * 100; + + glassSlider.style.width = `${percent}%`; + glassBlurLevel.textContent = `${clamped}px`; + + document.documentElement.style.setProperty( + "--glass-blur", + `${clamped}px` + ); + + localStorage.setItem("glassBlur", clamped); +} + +function setGlassEnabled(enabled) { + if (enabled) { + document.documentElement.classList.add("glass-enabled"); + localStorage.setItem("glassEnabled", "1"); + + if (glassBlurControl) glassBlurControl.classList.remove("disabled"); + if (glassSlider) glassSlider.setAttribute("aria-disabled", "false"); + if (glassCheckbox) glassCheckbox.setAttribute("aria-checked", "true"); + } else { + document.documentElement.classList.remove("glass-enabled"); + localStorage.setItem("glassEnabled", "0"); + + if (glassBlurControl) glassBlurControl.classList.add("disabled"); + if (glassSlider) glassSlider.setAttribute("aria-disabled", "true"); + if (glassCheckbox) glassCheckbox.setAttribute("aria-checked", "false"); + } +} + +/* ======================= + SLIDER DRAG HANDLING +======================= */ + +function handleGlassDrag(e) { + if (!glassBar) return; + + const clientX = e.type.startsWith("touch") + ? e.touches[0].clientX + : e.clientX; + + const rect = glassBar.getBoundingClientRect(); + let newPos = clientX - rect.left; + + newPos = Math.max(0, Math.min(rect.width, newPos)); + + const percentage = (newPos / rect.width) * 100; + const px = (percentage / 100) * MAX_BLUR; + + setGlassBlur(px); +} + +function startGlassDrag() { + const onMove = e => handleGlassDrag(e); + const onEnd = () => { + document.removeEventListener("mousemove", onMove); + document.removeEventListener("touchmove", onMove); + document.removeEventListener("mouseup", onEnd); + document.removeEventListener("touchend", onEnd); + }; + + document.addEventListener("mousemove", onMove); + document.addEventListener("touchmove", onMove); + document.addEventListener("mouseup", onEnd); + document.addEventListener("touchend", onEnd); +} + +/* ======================= + EVENT LISTENERS +======================= */ + +if (glassBar) { + ["mousedown", "touchstart"].forEach(evt => { + glassBar.addEventListener( + evt, + e => { + e.preventDefault(); + handleGlassDrag(e); + startGlassDrag(); + }, + { passive: false } + ); + }); +} + +if (glassCheckbox) { + glassCheckbox.setAttribute("role", "switch"); + glassCheckbox.setAttribute("aria-label", "Toggle glassmorphism"); + + glassCheckbox.addEventListener("change", () => { + setGlassEnabled(glassCheckbox.checked); + }); +} + +/* ======================= + INIT FROM STORAGE +======================= */ + +const savedGlassEnabled = localStorage.getItem("glassEnabled") === "1"; +const savedGlassBlur = Number(localStorage.getItem("glassBlur")) || 8; + +setGlassBlur(savedGlassBlur); +setGlassEnabled(savedGlassEnabled); + +if (glassCheckbox) { + glassCheckbox.checked = savedGlassEnabled; +} + +/* ======================= + PUBLIC API +======================= */ + +window.glassmorphism = { + setEnabled: setGlassEnabled, + setBlur: setGlassBlur +}; diff --git a/scripts/languages.js b/scripts/languages.js index 39c3f31..819e3cc 100644 --- a/scripts/languages.js +++ b/scripts/languages.js @@ -223,6 +223,13 @@ function applyLanguage(lang) { "dontShowTips", "aiSettingsIntro", "resetAISettingsBtn", + + /* Glassmorphism */ + "glassmorphismText", + "glassmorphismInfo", + "glassBlurTitle", + "glassBlurDesc", + "opacityTitle", "adjustOpacityDesc", "footerToastTitle", diff --git a/style.css b/style.css index 2756e7b..bfdf71f 100644 --- a/style.css +++ b/style.css @@ -34,6 +34,12 @@ --textColorDark-blue: #1b3041; --whitishColor-blue: #ffffff; + /* Glassmorphism variables */ + --glass-blur: 0px; /* applied when glass mode is enabled */ + --glass-saturation: 160%; + --glass-alpha: 0.6; + --glass-max-blur: 30px; + /* 🔴🔴🔴 */ --bg-color-red: #fdbdbd; --accentLightTint-red: #ffe7e7; @@ -1887,6 +1893,40 @@ body[data-bg="wallpaper"] .humidityBar .thinLine { font-size: 1rem; } + + +html.dark-theme.glass-enabled .bgLightTint, +html.dark-theme.glass-enabled .searchbar, +html.dark-theme.glass-enabled .tiles, +html.dark-theme.glass-enabled .lrectangle, +html.dark-theme.glass-enabled .modal-content, +html.dark-theme.glass-enabled .ai-modal-content { + background-color: rgba( + 0, + 0, + 0, + calc(var(--glass-alpha) - 0.12) + ); +} + + +/* Disabled state for controls (used when glass is off) */ +.opacityBarControl.disabled { + opacity: 0.55; + pointer-events: none; + filter: grayscale(0.18); +} +#glassBlurControl.disabled { + opacity: 0.55; + pointer-events: none; + filter: grayscale(0.18); +} + +/* Small helper for slider when aria-disabled is true */ +.opacityBar .opacitySlider[aria-disabled="true"] { + opacity: 0.6; +} + #opacityTextContainer { border-bottom: 0; padding: 8px 4px 6px;