From acd257bcabf42fdf33d3dc7b44ae5315ed54d1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:22:21 +0700 Subject: [PATCH 01/23] Update index.html --- index.html | 183 ++++++++++++++++++++++++++++------------------------- 1 file changed, 96 insertions(+), 87 deletions(-) diff --git a/index.html b/index.html index 9a5c048..3df955a 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,7 @@ Trung tâm điều khiển - Sentechtipvn - + @@ -23,9 +23,7 @@
- Quick -   - Control + Quick Control
@@ -59,17 +57,18 @@ - + +
-
+ +
-

⚙️ Cài đặt

- + +
CÀI ĐẶT
+
@@ -79,26 +78,27 @@

⚙️ Cài đặt

+ +
+ +
+
@@ -120,19 +120,13 @@

⚙️ Cài đặt

- +
-
- -
+
-
- -
-
- -
+
+
@@ -146,26 +140,18 @@

⚙️ Cài đặt

-
- Đậm nhạt màu nút - -
+
Đậm nhạt màu nút
- +
Black
Red
Green
Orange
- -
Purple
-
Moss
-
Glass
-
Convex glass
+
@@ -173,43 +159,57 @@

⚙️ Cài đặt

-
Khung
+
Khung
Bo góc
-
SVG
+
SVG
Độ đậm SVG
Nền khung
-
- Độ đậm nền -
+
Độ đậm nền
-
- -
+
Kích thước khung
Độ mờ khung
Cỡ nút bấm
+ +
Kích thước núm
+
Màu khung
Màu nút
Màu SVG
+ +
🏳️‍🌈 Màu núm kéo
+ + + +
-

🔘 Đổ Bóng

+

🔘 ĐỔ BÓNG

-
+
-
Kho Giao Diện
+
Kho Giao Diện
@@ -235,48 +235,57 @@

🔘 Đổ Bó

-
-
+ +
-
+
-
Thông tin ứng dụng
+
Quick Control
-
-
-

Quick Control

-

- Phát triển bởi - Sentechtipvn -

-

- Webclip điều khiển đa năng, tích hợp giao diện kính mờ và hệ thống Live Knobs tuỳ biến sống động chuyên nghiệp dành riêng cho iOS. -

+
+ + + -
-
-
-
-
-
-
-
-
Cấu hình
-
-
- -
- - + +
+

Quick Control

+ +
+ Phiên bản + 1.0 +
+ +
+ Phát triển bởi + Sentechtipvn +
+ +
+
📝 Mô tả chức năng
+

Webclip điều khiển đa năng, tích hợp giao diện kính mờ và hệ thống Live Knobs tuỳ biến sống động chuyên nghiệp dành riêng cho iOS.

+
+ +
+
📖 Hướng dẫn sử dụng
+

1. Bấm vào nút bất kỳ để kích hoạt tính năng tương ứng (Wi-Fi, Bluetooth, VPN, Đèn pin...).

2. Nhấn vào biểu tượng ⚙️ ở góc dưới phải để mở bảng Cài đặt tuỳ chỉnh.

3. Chọn Theme có sẵn trong preset hoặc tự do kéo slider để tùy chỉnh màu sắc, kích thước khung, đổ bóng, kính mờ theo gu của bạn.

4. Vào mục Giao diện → Xuất để lưu mã cấu hình, Nhập để khôi phục lại khi cần. Nên sao lưu mã trước khi xóa webclip.

5. Slider âm lượng & độ sáng cần cài Shortcut tên "QuickControl" trong app Shortcuts để hoạt động.

+
- - + + - + \ No newline at end of file From 9fdc561866885e1c2757079cffe39ab3d03ac2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:24:17 +0700 Subject: [PATCH 02/23] Update vi-VN.json --- Language/vi-VN.json | 84 +++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/Language/vi-VN.json b/Language/vi-VN.json index 201c709..7f1fd7a 100644 --- a/Language/vi-VN.json +++ b/Language/vi-VN.json @@ -1,29 +1,20 @@ { "app_title": "Quick Control", - "settings_title": "⚙️ CÀI ĐẶT", + "settings_title": "CÀI ĐẶT", "btn_list": "Danh sách", "btn_grid": "Lưới", "btn_export": "Xuất", "btn_import": "Nhập", "btn_edit_layout": "Sắp xếp", - "btn_clear_bg": "Xóa ảnh nền", - "btn_config_copy": "📋 Sao chép", - "btn_config_apply": "✅ Áp dụng", + "btn_reset_default": "Khôi phục mặc định", "lbl_theme_presets": "🪄 Giao diện", "opt_preset_none": "Tự do tùy chỉnh", "opt_preset_vn": "🇻🇳 VietOS", - "opt_preset_neon_light": "Neon Sáng", - "opt_preset_neon_dark": "Neon Tối", - "opt_preset_Glass_button": "Nút thuỷ tinh", - "opt_preset_Dark_blue": "Xanh tối", - "opt_preset_Neon_glow": "Phát sáng neon", - "opt_preset_Convex_glass": "Kính cầu lồi", - "opt_preset_Purple_glass": "Thuỷ tinh tím", - "lbl_popup_anim": "✨ Hiệu ứng Popup Cài đặt", - "opt_popup_default": "Trượt từ phải sang (Mặc định)", - "opt_popup_zoom": "Thu phóng (macOS)", - "opt_popup_slide_top": "Trượt từ trên xuống", - "opt_popup_fade": "Làm mờ (Fade)", + "opt_preset_Green_button": "Nút xanh lá", + "opt_preset_White_button_(iPhone)": "Nút trắng (iPhone)", + "opt_preset_White_button_(iPad)": "Nút trắng (iPad)", + "opt_preset_neon_dark": "🌑 Neon Tối", + "opt_preset_gold": "👑 Gold", "lbl_title_adjust": "Sửa tiêu đề", "lbl_title_size": "🔠 Cỡ chữ chính", "lbl_title_spacing": "📐 Dãn chữ chính", @@ -36,20 +27,36 @@ "lbl_parallax": "🧭 Gia tốc kế 3D", "lbl_bg_image": "📱 Hình nền tự chọn", "lbl_glassmorphism": "Bật Kính mờ", + "info_title": "Quick Control", + "info_dev_prefix": "Phát triển bởi", + "info_desc": "Webclip điều khiển đa năng, tích hợp giao diện kính mờ và hệ thống Live Knobs tuỳ biến sống động chuyên nghiệp dành riêng cho iOS.", + "info_pill_follow": "Theo dõi", + "info_pill_contact": "Liên hệ", + "info_pill_about": "Thông tin", + "info_app_name": "Quick Control", + "info_version_label": "Phiên bản", + "info_dev_label": "Phát triển bởi", + "info_desc_title": "📝 Mô tả chức năng", + "info_guide_title": "📖 Hướng dẫn sử dụng", + "info_guide_desc": "1. Bấm vào nút bất kỳ để kích hoạt tính năng tương ứng (Wi-Fi, Bluetooth, VPN, Đèn pin...).\n\n2. Nhấn vào biểu tượng ⚙️ ở góc dưới phải để mở bảng Cài đặt tuỳ chỉnh.\n\n3. Chọn Theme có sẵn trong preset hoặc tự do kéo slider để tùy chỉnh màu sắc, kích thước khung, đổ bóng, kính mờ theo gu của bạn.\n\n4. Vào mục Giao diện → Xuất để lưu mã cấu hình, Nhập để khôi phục lại khi cần. Nên sao lưu mã trước khi xóa webclip.\n\n5. Slider âm lượng & độ sáng cần cài Shortcut tên \"QuickControl\" trong app Shortcuts để hoạt động.", "lbl_bg_main": "🏳️‍🌈 Màu Nền Chính", "lbl_text_color": "🏳️‍🌈 Màu Chữ Hệ Thống", - "lbl_list_bg": "Màu nền Danh sách", - "lbl_list_text": "Màu chữ Danh sách", - "lbl_list_svg": "Màu SVG Danh sách", - "lbl_theme_frame": "💠 CHỦ ĐỀ", + "lbl_list_bg": "🏳️‍🌈 Màu nền Danh sách", + "lbl_list_text": "🏳️‍🌈 Màu chữ Danh sách", + "lbl_list_svg": "🏳️‍🌈 Màu SVG Danh sách", + "lbl_theme_frame": "💠 CHỦ ĐỀ HỆ THỐNG", "lbl_frame_svg_size": "🖼️ Khung Icon & SVG", - "lbl_frame_size": "Khung", - "lbl_svg_size": "SVG", "lbl_svg_opacity": "Độ đậm SVG", "lbl_frame_radius": "Bo góc", "lbl_frame_color": "Nền khung", "lbl_frame_bg_opacity": "Độ đậm nền", - "lbl_svg_color": "Màu SVG", + "lbl_popup_anim": "✨ Hiệu ứng Popup Cài đặt", + "opt_anim_default": "Trượt từ phải sang", + "opt_anim_zoom": "Thu phóng", + "opt_anim_slide_top": "Trượt từ trên xuống", + "opt_anim_fade": "Làm mờ", + "lbl_theme_store": "Kho Giao Diện", + "lbl_svg_color": "🏳️‍🌈 Màu SVG", "lbl_shadow_section": "🔘 ĐỔ BÓNG", "shadow_inset": "⚪️ Bóng Chìm", "shadow_outer": "⚪️ Bóng Ngoài", @@ -62,25 +69,20 @@ "slider_spread": "Lan rộng", "slider_color": "Màu Bóng", "slider_opacity": "Độ đậm bóng", - "lbl_layout_mode": "Danh sách/Lưới", + "lbl_layout_mode": "💠 Danh sách/Lưới", + "msg_copy_success": "📄 Đã sao chép mã cấu hình!", + "msg_prompt_import": "📥 Dán mã cấu hình vào đây:", "lbl_media_widget": "🎵 Trình Phát Nhạc", - "lbl_media_width": "Kích thước khung", + "lbl_media_width": "📐 Kích thước khung", "lbl_media_bg_opacity": "Độ mờ khung", - "lbl_media_btn_size": "Cỡ nút bấm", - "lbl_media_bg_color": "Màu khung", - "lbl_media_btn_color": "Màu nút", - "lbl_media_svg_color": "Màu SVG", - "msg_copy_success": "Đã sao chép mã cấu hình!", - "msg_prompt_import": "📥 Dán mã cấu hình vào đây:", - "modal_info_title": "Thông tin ứng dụng", - "modal_theme_title": "Kho Giao Diện", - "modal_config_title": "Cấu hình", - "modal_config_title_export": "📤 Xuất cấu hình", - "modal_config_title_import": "📥 Nhập cấu hình", - "config_placeholder": "Mã cấu hình...", - "info_title": "Quick Control", - "info_dev_prefix": "Phát triển bởi", - "info_desc": "Webclip điều khiển đa năng, tích hợp giao diện kính mờ và hệ thống Live Knobs tuỳ biến sống động chuyên nghiệp dành riêng cho iOS.", + "lbl_media_btn_size": "📐 Cỡ nút bấm", + "lbl_media_bg_color": "🏳️‍🌈 Màu khung", + "lbl_media_btn_color": "🏳️‍🌈 Màu nút", + "lbl_media_svg_color": "🏳️‍🌈 Màu SVG", + "lbl_media_theme_sync": "Đồng bộ Theme hệ thống", + "lbl_theme_dot": "💠 Chủ đề núm kéo", + "lbl_thumb_size": "📐 Kích thước núm", + "lbl_thumb_color": "🏳️‍🌈 Màu núm kéo", "lang_wifi": "Wi-Fi", "lang_bluetooth": "Bluetooth", "lang_cellular": "Di động", @@ -102,4 +104,4 @@ "lang_airdrop": "Airdrop", "lang_text_to_speech": "Văn bản thành giọng nói", "lang_speech_to_text": "Giọng nói thành văn bản" -} +} \ No newline at end of file From 54f94b4fefb6eb9c47a249ea93fb6f05271feacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:26:16 +0700 Subject: [PATCH 03/23] Update theme.js --- js/theme.js | 1152 ++++++++++++++++++++------------------------------- 1 file changed, 460 insertions(+), 692 deletions(-) diff --git a/js/theme.js b/js/theme.js index f394d17..b27fa24 100644 --- a/js/theme.js +++ b/js/theme.js @@ -3,24 +3,11 @@ const DEV_MODE = true; const SHADOW_MODES = [ { id: 'inset', nameKey: 'shadow_inset', name: 'Bóng Chìm', template: 'inset {x}px {y}px {b}px {s}px {c}' }, { id: 'outer', nameKey: 'shadow_outer', name: 'Bóng Ngoài', template: '{x}px {y}px {b}px {s}px {c}' }, - { id: 'soft', nameKey: 'shadow_soft', name: 'Mờ Diện Rộng', template: '{x}px {y}px {b}px {s}px {c}' }, - { id: 'hard', nameKey: 'shadow_hard', name: 'Nổi Khối 3D', template: '{x}px {y}px {b}px {s}px {c}' }, - { id: 'glow', nameKey: 'shadow_glow', name: 'Phát Sáng', template: '0px 0px {b}px {s}px {c}' } + { id: 'soft', nameKey: 'shadow_soft', name: 'Mờ Diện Rộng', template: '{x}px {y}px {b}px {s}px {c}' }, + { id: 'hard', nameKey: 'shadow_hard', name: 'Nổi Khối 3D', template: '{x}px {y}px {b}px {s}px {c}' }, + { id: 'glow', nameKey: 'shadow_glow', name: 'Phát Sáng', template: '0px 0px {b}px {s}px {c}' } ]; -function safeSetItem(key, value) { - try { - localStorage.setItem(key, value); - return true; - } catch (e) { - console.warn('localStorage.setItem failed:', key, e); - return false; - } -} -function safeGetItem(key) { - try { return localStorage.getItem(key); } catch (e) { return null; } -} - document.addEventListener("DOMContentLoaded", () => { const root = document.documentElement; const shadowContainer = document.getElementById('shadow-controls'); @@ -34,16 +21,14 @@ document.addEventListener("DOMContentLoaded", () => { introTextWrapper.classList.add(randomEffect); } - if (mainContainer) mainContainer.classList.add('intro-zoom'); + mainContainer.classList.add('intro-zoom'); window.addEventListener('load', () => { setTimeout(() => { - if (introScreen) { - introScreen.classList.add('dismiss'); - if (mainContainer) mainContainer.classList.remove('intro-zoom'); - setTimeout(() => { introScreen.style.display = 'none'; }, 1500); - } - }, 3000); + introScreen.classList.add('dismiss'); + mainContainer.classList.remove('intro-zoom'); + setTimeout(() => { introScreen.style.display = 'none'; }, 500); + }, 2000); }); if (!DEV_MODE) document.body.classList.add('standard-mode'); @@ -51,92 +36,80 @@ document.addEventListener("DOMContentLoaded", () => { const badgePreset = document.getElementById('badge-preset'); const badgeTheme = document.getElementById('badge-theme'); - if (safeGetItem('sttv_seen_preset_badge') === 'true') badgePreset?.classList.add('hidden'); - if (safeGetItem('sttv_seen_theme_badge') === 'true') badgeTheme?.classList.add('hidden'); - - document.querySelectorAll('input[type="range"]').forEach(input => { - const wrapper = document.createElement('div'); - wrapper.style.position = 'relative'; - wrapper.style.flex = '1'; - wrapper.style.display = 'flex'; - wrapper.style.alignItems = 'center'; - wrapper.style.width = '100%'; - - input.parentNode.insertBefore(wrapper, input); - wrapper.appendChild(input); - - const tooltip = document.createElement('div'); - tooltip.className = 'slider-tooltip'; - tooltip.innerText = input.value; - wrapper.appendChild(tooltip); - - const updateTooltip = () => { - tooltip.innerText = input.value; - tooltip.classList.add('show'); - const min = input.min ? parseFloat(input.min) : 0; - const max = input.max ? parseFloat(input.max) : 100; - const val = parseFloat(input.value); - let percent = ((val - min) / (max - min)) * 100; - if (percent < 5) percent = 5; - if (percent > 95) percent = 95; - tooltip.style.left = `calc(${percent}% - 12px)`; - }; + if (localStorage.getItem('sttv_seen_preset_badge') === 'true') badgePreset?.classList.add('hidden'); + if (localStorage.getItem('sttv_seen_theme_badge') === 'true') badgeTheme?.classList.add('hidden'); + + let globalTooltip = document.createElement('div'); + globalTooltip.className = 'slider-tooltip'; + document.body.appendChild(globalTooltip); + + let rafId = null; + document.addEventListener('input', (e) => { + if (e.target && e.target.type === 'range') { + const input = e.target; + if (rafId) cancelAnimationFrame(rafId); + rafId = requestAnimationFrame(() => { + globalTooltip.innerText = input.value; + globalTooltip.classList.add('show'); + + const rect = input.getBoundingClientRect(); + const min = parseFloat(input.min) || 0; + const max = parseFloat(input.max) || 100; + const val = parseFloat(input.value); + const percent = (val - min) / (max - min); + + const thumbOffset = 12.5 - (percent * 25); + const thumbX = rect.left + (percent * rect.width) + thumbOffset; + + globalTooltip.style.left = `${thumbX}px`; + globalTooltip.style.top = `${rect.top - 35}px`; + }); - input.addEventListener('input', updateTooltip); - input.addEventListener('pointerup', () => tooltip.classList.remove('show')); - input.addEventListener('touchend', () => tooltip.classList.remove('show')); - input.addEventListener('blur', () => tooltip.classList.remove('show')); - input.addEventListener('mouseleave', () => tooltip.classList.remove('show')); + playTick(); + if (e.target.id !== 'val-theme-preset' && e.target.id !== 'val-popup-anim') { + resetPresetToCustom(); + } + updateLiveVariables(false); + } }); - if (shadowContainer) { - SHADOW_MODES.forEach(mode => { - const div = document.createElement('div'); - div.className = 'shadow-item dev-only'; - div.innerHTML = ` -
- ${mode.name} - -
-
-
-
-
-
-
-
-
- `; - shadowContainer.appendChild(div); - }); - } + const hideTooltip = (e) => { + if (e.target && e.target.type === 'range') { + globalTooltip.classList.remove('show'); + } + }; - function applyShadowI18n() { - if (!window.i18nData) return; - document.querySelectorAll('#shadow-controls [data-i18n]').forEach(el => { - const key = el.getAttribute('data-i18n'); - if (window.i18nData[key]) el.innerText = window.i18nData[key]; - }); - } - applyShadowI18n(); + document.addEventListener('pointerup', hideTooltip); + document.addEventListener('touchend', hideTooltip); + document.addEventListener('change', (e) => { + if (e.target && e.target.type === 'range') { + hideTooltip(e); + saveSettingsToLocal(); + } + }); - document.querySelectorAll('.theme-chip').forEach(chip => { - chip.addEventListener('click', () => { - document.querySelectorAll('.theme-chip').forEach(c => c.classList.remove('active')); - const val = chip.dataset.value; - document.querySelectorAll(`.theme-chip[data-value="${val}"]`).forEach(c => c.classList.add('active')); - const hiddenFrame = document.getElementById('val-theme-frame'); - if (hiddenFrame) hiddenFrame.value = val; - - if (badgeTheme) { - badgeTheme.classList.add('hidden'); - safeSetItem('sttv_seen_theme_badge', 'true'); - } - resetPresetToCustom(); - updateLiveVariables(); - }); + SHADOW_MODES.forEach(mode => { + const div = document.createElement('div'); + div.className = 'shadow-item dev-only'; + div.innerHTML = ` +
+ ${mode.name} + +
+
+
+
+
+
+
+
+
+ `; + shadowContainer.appendChild(div); }); + let currentThemeTarget = 'frame'; + function syncThemePickerVisuals(val) { document.querySelectorAll('.theme-chip').forEach(c => { if (c.dataset.value === val) c.classList.add('active'); @@ -146,301 +119,172 @@ document.addEventListener("DOMContentLoaded", () => { const themeModal = document.getElementById('theme-modal'); const themeOverlay = document.getElementById('theme-overlay'); + const btnMoreThemes = document.getElementById('btn-more-themes'); - const closeThemeModal = document.getElementById('close-theme-modal'); - - if (btnMoreThemes && themeModal && themeOverlay) { - btnMoreThemes.onclick = () => { themeModal.classList.add('show'); themeOverlay.classList.add('show'); }; - } - if (closeThemeModal && themeModal && themeOverlay) { - closeThemeModal.onclick = () => { themeModal.classList.remove('show'); themeOverlay.classList.remove('show'); }; + if (btnMoreThemes) { + btnMoreThemes.onclick = () => { + currentThemeTarget = 'frame'; + syncThemePickerVisuals(document.getElementById('val-theme-frame').value); + themeModal.classList.add('show'); themeOverlay.classList.add('show'); + }; } - if (themeOverlay && themeModal) { - themeOverlay.onclick = () => { themeModal.classList.remove('show'); themeOverlay.classList.remove('show'); }; + + const btnThumbTheme = document.getElementById('btn-thumb-theme'); + if (btnThumbTheme) { + btnThumbTheme.onclick = () => { + currentThemeTarget = 'thumb'; + syncThemePickerVisuals(document.getElementById('val-thumb-theme').value); + themeModal.classList.add('show'); themeOverlay.classList.add('show'); + }; } - const presetSelect = document.getElementById('val-theme-preset'); - if (presetSelect) { - presetSelect.addEventListener('change', (e) => { - const val = e.target.value; - safeSetItem('sttv_activePreset', val); - if (badgePreset) { - badgePreset.classList.add('hidden'); - safeSetItem('sttv_seen_preset_badge', 'true'); + document.querySelectorAll('.theme-chip').forEach(chip => { + chip.addEventListener('click', () => { + const val = chip.dataset.value; + syncThemePickerVisuals(val); + + if (currentThemeTarget === 'frame') { + document.getElementById('val-theme-frame').value = val; + if (badgeTheme) { badgeTheme.classList.add('hidden'); localStorage.setItem('sttv_seen_theme_badge', 'true'); } + resetPresetToCustom(); + } else if (currentThemeTarget === 'thumb') { + document.getElementById('val-thumb-theme').value = val; } - if (val !== 'none') unpackConfig(val); + updateLiveVariables(true); }); + }); + + const closeThemeModal = document.getElementById('close-theme-modal'); + if (closeThemeModal) closeThemeModal.onclick = () => { themeModal.classList.remove('show'); themeOverlay.classList.remove('show'); }; + + const infoModal = document.getElementById('info-modal'); + const btnInfo = document.getElementById('btn-info'); + if (btnInfo && infoModal) btnInfo.onclick = () => { infoModal.classList.add('show'); themeOverlay.classList.add('show'); }; + const closeInfoModal = document.getElementById('close-info-modal'); + if (closeInfoModal && infoModal) closeInfoModal.onclick = () => { infoModal.classList.remove('show'); themeOverlay.classList.remove('show'); }; + + if (themeOverlay) { + themeOverlay.onclick = () => { + if(themeModal) themeModal.classList.remove('show'); + if(infoModal) infoModal.classList.remove('show'); + themeOverlay.classList.remove('show'); + }; } + const presetSelect = document.getElementById('val-theme-preset'); + presetSelect.addEventListener('change', (e) => { + const val = e.target.value; + localStorage.setItem('sttv_activePreset', val); + if (badgePreset) { badgePreset.classList.add('hidden'); localStorage.setItem('sttv_seen_preset_badge', 'true'); } + if (val !== 'none') unpackConfig(val); + }); + function resetPresetToCustom() { - if (presetSelect) { - presetSelect.value = 'none'; - safeSetItem('sttv_activePreset', 'none'); - } + presetSelect.value = 'none'; + localStorage.setItem('sttv_activePreset', 'none'); } const drawerEl = document.getElementById('settings-drawer'); const popupAnimSelect = document.getElementById('val-popup-anim'); - if (popupAnimSelect && drawerEl) { - popupAnimSelect.addEventListener('change', (e) => { - const animClass = e.target.value; - drawerEl.className = 'settings-drawer ' + (drawerEl.classList.contains('open') ? 'open ' : '') + (animClass !== 'default' ? animClass : ''); - safeSetItem('sttv_popupAnim', animClass); - }); - } + popupAnimSelect.addEventListener('change', (e) => { + const animClass = e.target.value; + drawerEl.className = 'settings-drawer ' + (drawerEl.classList.contains('open') ? 'open ' : '') + (animClass !== 'default' ? animClass : ''); + localStorage.setItem('sttv_popupAnim', animClass); + }); const AudioContext = window.AudioContext || window.webkitAudioContext; - let audioCtx; + let audioCtx = null; function playTick() { - const toggleAudio = document.getElementById('toggle-audio'); - if (!toggleAudio || !toggleAudio.checked) return; - try { - if (!audioCtx) audioCtx = new AudioContext(); - if (audioCtx.state === 'suspended') audioCtx.resume(); - const osc = audioCtx.createOscillator(); - const gain = audioCtx.createGain(); - osc.type = 'sine'; - osc.frequency.setValueAtTime(800, audioCtx.currentTime); - osc.frequency.exponentialRampToValueAtTime(300, audioCtx.currentTime + 0.05); - gain.gain.setValueAtTime(0.3, audioCtx.currentTime); - gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.05); - osc.connect(gain); gain.connect(audioCtx.destination); - osc.start(); osc.stop(audioCtx.currentTime + 0.05); - } catch(e) {} - } - document.addEventListener('click', (e) => { - if (e.target.tagName === 'BUTTON' || e.target.type === 'checkbox' || e.target.closest('.theme-chip') || e.target.closest('.theme-chip-more')) playTick(); + if (!document.getElementById('toggle-audio').checked) return; + if (!audioCtx) audioCtx = new AudioContext(); + if (audioCtx.state === 'suspended') audioCtx.resume(); + const osc = audioCtx.createOscillator(); + const gain = audioCtx.createGain(); + osc.type = 'sine'; osc.frequency.setValueAtTime(800, audioCtx.currentTime); osc.frequency.exponentialRampToValueAtTime(300, audioCtx.currentTime + 0.04); + gain.gain.setValueAtTime(0.2, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.04); + osc.connect(gain); gain.connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + 0.04); + } + document.addEventListener('click', (e) => { if (e.target.tagName === 'BUTTON' || e.target.type === 'checkbox' || e.target.closest('.theme-chip') || e.target.closest('.theme-chip-more')) playTick(); }); + + drawerEl.addEventListener('input', (e) => { if (e.target.type === 'color') updateLiveVariables(true); }); + drawerEl.addEventListener('change', (e) => { + if (e.target.type === 'color') { e.target.blur(); updateLiveVariables(true); } + else if (e.target.type === 'checkbox') updateLiveVariables(true); }); - - if (drawerEl) { - drawerEl.addEventListener('input', (e) => { - if (e.target.type === 'range') playTick(); - if (e.target.id !== 'val-theme-preset' && e.target.id !== 'val-popup-anim') resetPresetToCustom(); - }); - } document.querySelectorAll('.shadow-switch').forEach(switchBtn => { switchBtn.addEventListener('change', (e) => { const drawer = document.getElementById(`drawer-${e.target.value}`); - if (drawer) { - if (e.target.checked) drawer.classList.add('active'); - else drawer.classList.remove('active'); - } - resetPresetToCustom(); - updateLiveVariables(); + if (e.target.checked) drawer.classList.add('active'); else drawer.classList.remove('active'); + resetPresetToCustom(); updateLiveVariables(true); }); }); function handleOrientation(e) { - const toggleParallax = document.getElementById('toggle-parallax'); - if (!toggleParallax || !toggleParallax.checked) return; + if (!document.getElementById('toggle-parallax').checked) return; let x = e.gamma; let y = e.beta; if (x > 45) x = 45; if (x < -45) x = -45; if (y > 45) y = 45; if (y < -45) y = -45; - root.style.setProperty('--tilt-x', (x / 10) + 'px'); - root.style.setProperty('--tilt-y', (y / 10) + 'px'); + root.style.setProperty('--tilt-x', (x / 10) + 'px'); root.style.setProperty('--tilt-y', (y / 10) + 'px'); } - const toggleParallax = document.getElementById('toggle-parallax'); - if (toggleParallax) { - toggleParallax.addEventListener('change', (e) => { - if (e.target.checked) { - if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') { - DeviceOrientationEvent.requestPermission().then(state => { - if (state === 'granted') window.addEventListener('deviceorientation', handleOrientation); - else { e.target.checked = false; alert('Vui lòng cấp quyền cảm biến!'); } - }).catch(console.error); - } else { - window.addEventListener('deviceorientation', handleOrientation); - } - } else { - window.removeEventListener('deviceorientation', handleOrientation); - root.style.setProperty('--tilt-x', '0px'); - root.style.setProperty('--tilt-y', '0px'); - } - safeSetItem('sttv_parallax', e.target.checked); - }); - } + document.getElementById('toggle-parallax').addEventListener('change', (e) => { + if (e.target.checked) { + if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') { + DeviceOrientationEvent.requestPermission().then(state => { + if (state === 'granted') window.addEventListener('deviceorientation', handleOrientation); + else { e.target.checked = false; alert('Vui lòng cấp quyền cảm biến!'); } + }).catch(console.error); + } else { window.addEventListener('deviceorientation', handleOrientation); } + } else { + window.removeEventListener('deviceorientation', handleOrientation); + root.style.setProperty('--tilt-x', '0px'); root.style.setProperty('--tilt-y', '0px'); + } + localStorage.setItem('sttv_parallax', e.target.checked); + }); const uploadBg = document.getElementById('upload-bg'); - if (uploadBg) { - uploadBg.addEventListener('change', (e) => { - const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (ev) => { - const b64 = ev.target.result; - const ok = safeSetItem('sttv_customBgImage', b64); - if (!ok) { - alert('⚠️ Ảnh quá lớn, không thể lưu vào bộ nhớ. Vui lòng chọn ảnh nhỏ hơn!'); - return; - } - root.style.setProperty('--bg-image', `url('${b64}')`); - }; - reader.readAsDataURL(file); - } - }); - } - const clearBgBtn = document.getElementById('clear-bg'); - if (clearBgBtn) { - clearBgBtn.addEventListener('click', () => { - try { localStorage.removeItem('sttv_customBgImage'); } catch(e) {} - root.style.setProperty('--bg-image', 'none'); - if (uploadBg) uploadBg.value = ""; - }); - } + uploadBg.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (ev) => { + const b64 = ev.target.result; localStorage.setItem('sttv_customBgImage', b64); + root.style.setProperty('--bg-image', `url('${b64}')`); + }; + reader.readAsDataURL(file); + } + }); + document.getElementById('clear-bg').addEventListener('click', () => { + localStorage.removeItem('sttv_customBgImage'); root.style.setProperty('--bg-image', 'none'); uploadBg.value = ""; + }); const overlay = document.getElementById('settings-overlay'); - const closeSettings = () => { - if (drawerEl) drawerEl.classList.remove('open'); - if (overlay) overlay.classList.remove('open'); - }; - const openSettingsBtn = document.getElementById('open-settings'); - const closeSettingsBtn = document.getElementById('close-settings'); - if (openSettingsBtn && drawerEl && overlay) { - openSettingsBtn.onclick = () => { drawerEl.classList.add('open'); overlay.classList.add('open'); }; - } - if (closeSettingsBtn) closeSettingsBtn.onclick = closeSettings; - if (overlay) overlay.onclick = closeSettings; + const closeSettings = () => { drawerEl.classList.remove('open'); overlay.classList.remove('open'); }; + document.getElementById('open-settings').onclick = () => { drawerEl.classList.add('open'); overlay.classList.add('open'); }; + document.getElementById('close-settings').onclick = closeSettings; + overlay.onclick = closeSettings; let adjustTimeout; - if (drawerEl) { - drawerEl.addEventListener('input', (e) => { - if (e.target.tagName === 'INPUT') { - drawerEl.classList.add('adjusting'); - clearTimeout(adjustTimeout); - adjustTimeout = setTimeout(() => drawerEl.classList.remove('adjusting'), 800); - } - updateLiveVariables(); - }); - } - - const btnList = document.getElementById('btn-layout-list'); - const btnGrid = document.getElementById('btn-layout-grid'); - function setLayoutMode(mode) { - safeSetItem('sttv_layoutMode', mode); - if (!btnList || !btnGrid) return; - if (mode === 'list') { btnList.classList.add('active'); btnGrid.classList.remove('active'); } - else { btnGrid.classList.add('active'); btnList.classList.remove('active'); } - updateLiveVariables(); - } - if (btnList) btnList.onclick = () => { setLayoutMode('list'); resetPresetToCustom(); }; - if (btnGrid) btnGrid.onclick = () => { setLayoutMode('grid'); resetPresetToCustom(); }; - - const infoModal = document.getElementById('info-modal'); - const infoOverlay = document.getElementById('info-overlay'); - const btnInfo = document.getElementById('btn-info'); - const closeInfoBtn = document.getElementById('close-info'); - - if (btnInfo && infoModal && infoOverlay) { - btnInfo.onclick = () => { - infoModal.classList.add('show'); - infoOverlay.classList.add('show'); - }; - } - if (closeInfoBtn && infoModal && infoOverlay) { - closeInfoBtn.onclick = () => { - infoModal.classList.remove('show'); - infoOverlay.classList.remove('show'); - }; - } - if (infoOverlay && infoModal) { - infoOverlay.onclick = (e) => { - if (e.target.id === 'info-overlay') { - infoModal.classList.remove('show'); - infoOverlay.classList.remove('show'); - } - }; - } - - const configModal = document.getElementById('config-modal'); - const configOverlay = document.getElementById('config-overlay'); - const configTextarea = document.getElementById('config-textarea'); - const configModalTitle = document.getElementById('config-modal-title'); - const btnConfigCopy = document.getElementById('btn-config-copy'); - const btnConfigApply = document.getElementById('btn-config-apply'); - const btnConfigClose = document.getElementById('close-config'); - - function openConfigModal(mode) { - if (!configModal || !configOverlay) return; - if (mode === 'export') { - configTextarea.value = packConfig(); - configTextarea.readOnly = true; - configModalTitle.innerText = (window.i18nData && window.i18nData['modal_config_title_export']) || '📤 Xuất cấu hình'; - btnConfigCopy.style.display = 'block'; - btnConfigApply.style.display = 'none'; - } else { - configTextarea.value = ''; - configTextarea.readOnly = false; - configModalTitle.innerText = (window.i18nData && window.i18nData['modal_config_title_import']) || '📥 Nhập cấu hình'; - btnConfigCopy.style.display = 'none'; - btnConfigApply.style.display = 'block'; + drawerEl.addEventListener('input', (e) => { + if (e.target.tagName === 'INPUT' && e.target.type !== 'color') { + drawerEl.classList.add('adjusting'); clearTimeout(adjustTimeout); + adjustTimeout = setTimeout(() => drawerEl.classList.remove('adjusting'), 800); } - configModal.classList.add('show'); - configOverlay.classList.add('show'); - setTimeout(() => { - try { - if (mode === 'export') configTextarea.select(); - else configTextarea.focus(); - } catch(e) {} - }, 250); - } - function closeConfigModal() { - if (configModal) configModal.classList.remove('show'); - if (configOverlay) configOverlay.classList.remove('show'); - } - - if (btnConfigClose) btnConfigClose.onclick = closeConfigModal; - if (configOverlay) configOverlay.onclick = closeConfigModal; - - if (btnConfigCopy) { - btnConfigCopy.onclick = () => { - const text = configTextarea.value; - const successMsg = (window.i18nData && window.i18nData['msg_copy_success']) || "Đã sao chép mã cấu hình!"; - - const doFallbackCopy = () => { - try { - const wasReadOnly = configTextarea.readOnly; - configTextarea.readOnly = false; - configTextarea.select(); - configTextarea.setSelectionRange(0, 99999); - const ok = document.execCommand('copy'); - configTextarea.readOnly = wasReadOnly; - if (ok) alert(successMsg); - else alert('Vui lòng nhấn giữ để chọn và sao chép thủ công.'); - } catch(e) { - alert('Vui lòng nhấn giữ để chọn và sao chép thủ công.'); - } - }; - - if (navigator.clipboard && window.isSecureContext) { - navigator.clipboard.writeText(text).then(() => alert(successMsg)).catch(doFallbackCopy); - } else { - doFallbackCopy(); - } - }; - } + }); - if (btnConfigApply) { - btnConfigApply.onclick = () => { - const code = configTextarea.value.trim(); - if (!code) { alert('Vui lòng dán mã cấu hình!'); return; } - unpackConfig(code); - resetPresetToCustom(); - closeConfigModal(); - }; - } + function setLayoutMode(mode) { localStorage.setItem('sttv_layoutMode', mode); updateLiveVariables(true); } + document.getElementById('btn-layout-list').onclick = () => { setLayoutMode('list'); resetPresetToCustom(); }; + document.getElementById('btn-layout-grid').onclick = () => { setLayoutMode('grid'); resetPresetToCustom(); }; function hexToRgba(hex, alpha) { let r = 0, g = 0, b = 0; - if (!hex) return `rgba(0,0,0,${alpha / 100})`; if (hex.length === 4) { r = parseInt(hex[1] + hex[1], 16); g = parseInt(hex[2] + hex[2], 16); b = parseInt(hex[3] + hex[3], 16); } else if (hex.length === 7) { r = parseInt(hex.substring(1, 3), 16); g = parseInt(hex.substring(3, 5), 16); b = parseInt(hex.substring(5, 7), 16); } return `rgba(${r}, ${g}, ${b}, ${alpha / 100})`; } function hexToRgb(hex) { let r = 0, g = 0, b = 0; - if (!hex) return '0,0,0'; if (hex.length === 4) { r = parseInt(hex[1] + hex[1], 16); g = parseInt(hex[2] + hex[2], 16); b = parseInt(hex[3] + hex[3], 16); } else if (hex.length === 7) { r = parseInt(hex.substring(1, 3), 16); g = parseInt(hex.substring(3, 5), 16); b = parseInt(hex.substring(5, 7), 16); } return `${r}, ${g}, ${b}`; @@ -453,51 +297,53 @@ document.addEventListener("DOMContentLoaded", () => { const drawer = document.getElementById(`drawer-${checkbox.value}`); if (!drawer) return; const mode = SHADOW_MODES.find(m => m.id === checkbox.value); - const hexColor = drawer.querySelector('.s-c').value; - const opacity = drawer.querySelector('.s-o').value; - const rgbaColor = hexToRgba(hexColor, opacity); - + const rgbaColor = hexToRgba(drawer.querySelector('.s-c').value, drawer.querySelector('.s-o').value); let shadowStr = mode.template .replace('{x}', drawer.querySelector('.s-x').value).replace('{y}', drawer.querySelector('.s-y').value) .replace('{b}', drawer.querySelector('.s-b').value).replace('{s}', drawer.querySelector('.s-s').value) .replace('{c}', rgbaColor); - if (combinedShadow) combinedShadow += ', '; - combinedShadow += shadowStr; + if (combinedShadow) combinedShadow += ', '; combinedShadow += shadowStr; }); root.style.setProperty('--btn-shadow', combinedShadow || 'none'); } function saveSettingsToLocal() { - const get = id => document.getElementById(id); + localStorage.setItem('sttv_bgMain', document.getElementById('val-bg-main').value); + localStorage.setItem('sttv_textColor', document.getElementById('val-text-color').value); + localStorage.setItem('sttv_listBg', document.getElementById('val-list-bg').value); + localStorage.setItem('sttv_listBgOpacity', document.getElementById('val-list-bg-opacity').value); + localStorage.setItem('sttv_listText', document.getElementById('val-list-text').value); + localStorage.setItem('sttv_listSvg', document.getElementById('val-list-svg').value); + localStorage.setItem('sttv_themeFrame', document.getElementById('val-theme-frame').value); + localStorage.setItem('sttv_frameSize', document.getElementById('val-frame-size').value); + localStorage.setItem('sttv_svgSize', document.getElementById('val-svg-size').value); + localStorage.setItem('sttv_svgOpacity', document.getElementById('val-svg-opacity').value); + localStorage.setItem('sttv_frameRadius', document.getElementById('val-frame-radius').value); + localStorage.setItem('sttv_frameColor', document.getElementById('val-frame-color').value); + localStorage.setItem('sttv_frameBgOpacity', document.getElementById('val-frame-bg-opacity').value); + localStorage.setItem('sttv_svgColor', document.getElementById('val-svg-color').value); - const saves = { - sttv_bgMain: get('val-bg-main'), sttv_textColor: get('val-text-color'), - sttv_listBg: get('val-list-bg'), sttv_listBgOpacity: get('val-list-bg-opacity'), - sttv_listText: get('val-list-text'), sttv_listSvg: get('val-list-svg'), - sttv_themeFrame: get('val-theme-frame'), - sttv_frameSize: get('val-frame-size'), sttv_svgSize: get('val-svg-size'), - sttv_svgOpacity: get('val-svg-opacity'), sttv_frameRadius: get('val-frame-radius'), - sttv_frameColor: get('val-frame-color'), sttv_frameBgOpacity: get('val-frame-bg-opacity'), - sttv_svgColor: get('val-svg-color'), - sttv_titleSize: get('val-title-size'), sttv_titleSpacing: get('val-title-spacing'), - sttv_iconSize: get('val-icon-size'), sttv_iconSpacing: get('val-icon-spacing'), - sttv_mediaWidth: get('val-media-width'), sttv_mediaBgOpacity: get('val-media-bg-opacity'), - sttv_mediaBtnSize: get('val-media-btn-size'), sttv_mediaBgColor: get('val-media-bg-color'), - sttv_mediaBtnColor: get('val-media-btn-color'), sttv_mediaSvgColor: get('val-media-svg-color') - }; - for (const k in saves) { - if (saves[k]) safeSetItem(k, saves[k].value); - } - - const checks = { - sttv_hideLabels: get('toggle-hide-labels'), - sttv_listFrame: get('toggle-list-frame'), - sttv_glassMode: get('toggle-glass'), - sttv_audioFeedback: get('toggle-audio') - }; - for (const k in checks) { - if (checks[k]) safeSetItem(k, checks[k].checked); - } + localStorage.setItem('sttv_hideLabels', document.getElementById('toggle-hide-labels').checked); + localStorage.setItem('sttv_listFrame', document.getElementById('toggle-list-frame').checked); + localStorage.setItem('sttv_titleSize', document.getElementById('val-title-size').value); + localStorage.setItem('sttv_titleSpacing', document.getElementById('val-title-spacing').value); + localStorage.setItem('sttv_iconSize', document.getElementById('val-icon-size').value); + localStorage.setItem('sttv_iconSpacing', document.getElementById('val-icon-spacing').value); + localStorage.setItem('sttv_glassMode', document.getElementById('toggle-glass').checked); + localStorage.setItem('sttv_audioFeedback', document.getElementById('toggle-audio').checked); + + localStorage.setItem('sttv_mediaWidth', document.getElementById('val-media-width').value); + localStorage.setItem('sttv_mediaBgOpacity', document.getElementById('val-media-bg-opacity').value); + localStorage.setItem('sttv_mediaBtnSize', document.getElementById('val-media-btn-size').value); + localStorage.setItem('sttv_mediaBgColor', document.getElementById('val-media-bg-color').value); + localStorage.setItem('sttv_mediaBtnColor', document.getElementById('val-media-btn-color').value); + localStorage.setItem('sttv_mediaSvgColor', document.getElementById('val-media-svg-color').value); + localStorage.setItem('sttv_mediaThemeSync', document.getElementById('toggle-media-theme').checked); + localStorage.setItem('sttv_themeDot', document.getElementById('toggle-theme-dot').checked); + + localStorage.setItem('sttv_thumbSize', document.getElementById('val-thumb-size').value); + localStorage.setItem('sttv_thumbColor', document.getElementById('val-thumb-color').value); + localStorage.setItem('sttv_thumbTheme', document.getElementById('val-thumb-theme').value); const shadowState = {}; SHADOW_MODES.forEach(mode => { @@ -505,376 +351,298 @@ document.addEventListener("DOMContentLoaded", () => { const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode.id}"]`); if (drawer && checkbox) { shadowState[mode.id] = { - active: checkbox.checked, - x: drawer.querySelector('.s-x').value, y: drawer.querySelector('.s-y').value, + active: checkbox.checked, x: drawer.querySelector('.s-x').value, y: drawer.querySelector('.s-y').value, b: drawer.querySelector('.s-b').value, s: drawer.querySelector('.s-s').value, c: drawer.querySelector('.s-c').value, o: drawer.querySelector('.s-o').value }; } }); - safeSetItem('sttv_shadowConfig', JSON.stringify(shadowState)); + localStorage.setItem('sttv_shadowConfig', JSON.stringify(shadowState)); } function packConfig() { - const shadowState = {}; + const shadowArr = []; SHADOW_MODES.forEach(mode => { const drawer = document.getElementById(`drawer-${mode.id}`); const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode.id}"]`); - if (drawer && checkbox) { - shadowState[mode.id] = { - active: checkbox.checked, - x: drawer.querySelector('.s-x').value, y: drawer.querySelector('.s-y').value, - b: drawer.querySelector('.s-b').value, s: drawer.querySelector('.s-s').value, - c: drawer.querySelector('.s-c').value, o: drawer.querySelector('.s-o').value - }; + if (drawer && checkbox && checkbox.checked) { + shadowArr.push([ + mode.id, drawer.querySelector('.s-x').value, drawer.querySelector('.s-y').value, + drawer.querySelector('.s-b').value, drawer.querySelector('.s-s').value, + drawer.querySelector('.s-c').value, drawer.querySelector('.s-o').value + ].join('*')); } }); - const g = id => { const el = document.getElementById(id); return el ? el.value : ''; }; - const c = id => { const el = document.getElementById(id); return el ? el.checked : false; }; - - const fullConfig = { - bgMain: g('val-bg-main'), textColor: g('val-text-color'), - layoutMode: safeGetItem('sttv_layoutMode') || 'grid', - listBg: g('val-list-bg'), listText: g('val-list-text'), listSvg: g('val-list-svg'), - themeFrame: g('val-theme-frame'), frameSize: g('val-frame-size'), - svgSize: g('val-svg-size'), svgOpacity: g('val-svg-opacity'), - frameRadius: g('val-frame-radius'), frameColor: g('val-frame-color'), - svgColor: g('val-svg-color'), - hideLabels: c('toggle-hide-labels'), titleSize: g('val-title-size'), - titleSpacing: g('val-title-spacing'), listFrame: c('toggle-list-frame'), - iconSize: g('val-icon-size'), iconSpacing: g('val-icon-spacing'), - listBgOpacity: g('val-list-bg-opacity'), frameBgOpacity: g('val-frame-bg-opacity'), - mediaWidth: g('val-media-width'), mediaBgOpacity: g('val-media-bg-opacity'), - mediaBtnSize: g('val-media-btn-size'), mediaBgColor: g('val-media-bg-color'), - mediaBtnColor: g('val-media-btn-color'), mediaSvgColor: g('val-media-svg-color'), - glassMode: c('toggle-glass'), popupAnim: g('val-popup-anim'), - shadowConfig: shadowState - }; - - return btoa(unescape(encodeURIComponent(JSON.stringify(fullConfig)))); - } - - function unpackConfig(base64Str) { + const configValues = [ + document.getElementById('val-bg-main').value, document.getElementById('val-text-color').value, + localStorage.getItem('sttv_layoutMode') || 'grid', document.getElementById('val-list-bg').value, + document.getElementById('val-list-text').value, document.getElementById('val-list-svg').value, + document.getElementById('val-theme-frame').value, document.getElementById('val-frame-size').value, + document.getElementById('val-svg-size').value, document.getElementById('val-svg-opacity').value, + document.getElementById('val-frame-radius').value, document.getElementById('val-frame-color').value, + document.getElementById('val-svg-color').value, document.getElementById('toggle-hide-labels').checked ? 1 : 0, + document.getElementById('val-title-size').value, document.getElementById('val-title-spacing').value, + document.getElementById('toggle-list-frame').checked ? 1 : 0, document.getElementById('val-icon-size').value, + document.getElementById('val-icon-spacing').value, document.getElementById('val-list-bg-opacity').value, + document.getElementById('val-frame-bg-opacity').value, document.getElementById('val-media-width').value, + document.getElementById('val-media-bg-opacity').value, document.getElementById('val-media-btn-size').value, + document.getElementById('val-media-bg-color').value, document.getElementById('val-media-btn-color').value, + document.getElementById('val-media-svg-color').value, document.getElementById('toggle-glass').checked ? 1 : 0, + document.getElementById('val-popup-anim').value, shadowArr.join('~'), + document.getElementById('toggle-media-theme').checked ? 1 : 0, + document.getElementById('toggle-theme-dot').checked ? 1 : 0, + document.getElementById('val-thumb-size').value, + document.getElementById('val-thumb-color').value, + document.getElementById('val-thumb-theme').value + ]; + return encodeURIComponent(configValues.join('|')); + } + + function unpackConfig(code) { + if (!code) return; try { - if (drawerEl) drawerEl.classList.remove('adjusting'); - const decodedStr = decodeURIComponent(escape(atob(base64Str))); - let config; - try { config = JSON.parse(decodedStr); } - catch(e) { alert("Mã không hợp lệ!"); return; } - - try { localStorage.removeItem('sttv_customBgImage'); } catch(e) {} + drawerEl.classList.remove('adjusting'); localStorage.removeItem('sttv_customBgImage'); root.style.setProperty('--bg-image', 'none'); - const uploadBgInput = document.getElementById('upload-bg'); - if (uploadBgInput) uploadBgInput.value = ""; - - const setV = (id, v) => { const el = document.getElementById(id); if (el) el.value = v; }; - const setC = (id, v) => { const el = document.getElementById(id); if (el) el.checked = v; }; - - if (Array.isArray(config)) { - if (config.length < 13) throw 'Lỗi mã cũ'; - if (config[0]) { setV('val-bg-main', config[0]); root.style.setProperty('--bg-main', config[0]); } - setV('val-text-color', config[1]); - setLayoutMode(config[2]); - setV('val-list-bg', config[3]); - setV('val-list-text', config[4]); - setV('val-list-svg', config[5]); - setV('val-theme-frame', config[6]); - syncThemePickerVisuals(config[6]); - setV('val-frame-size', config[7]); - setV('val-svg-size', config[8]); - setV('val-svg-opacity', config[9]); - setV('val-frame-radius', config[10]); - setV('val-frame-color', config[11]); - setV('val-svg-color', config[12]); - - document.querySelectorAll('.shadow-switch').forEach(cb => { - cb.checked = false; - const d = document.getElementById(`drawer-${cb.value}`); - if (d) d.classList.remove('active'); - }); - if (config[13] && config[13].length > 0) { - const sId = config[13][0]; - const shadowSwitch = document.querySelector(`input[name="active_shadow"][value="${sId}"]`); - if (shadowSwitch) { - shadowSwitch.checked = true; - const drw = document.getElementById(`drawer-${sId}`); - if (drw) { - drw.classList.add('active'); - drw.querySelector('.s-x').value = config[13][1]; - drw.querySelector('.s-y').value = config[13][2]; - drw.querySelector('.s-b').value = config[13][3]; - drw.querySelector('.s-s').value = config[13][4]; - drw.querySelector('.s-c').value = config[13][5]; - if (config[13].length > 6) drw.querySelector('.s-o').value = config[13][6]; + if (document.getElementById('upload-bg')) document.getElementById('upload-bg').value = ""; + + const decoded = decodeURIComponent(code.trim()); + const data = decoded.split('|'); + if (data.length < 29) { alert("Mã cấu hình không hợp lệ!"); return; } + + document.getElementById('val-bg-main').value = data[0]; document.getElementById('val-text-color').value = data[1]; + setLayoutMode(data[2]); document.getElementById('val-list-bg').value = data[3]; + document.getElementById('val-list-text').value = data[4]; document.getElementById('val-list-svg').value = data[5]; + document.getElementById('val-theme-frame').value = data[6]; + document.getElementById('val-frame-size').value = data[7]; document.getElementById('val-svg-size').value = data[8]; + document.getElementById('val-svg-opacity').value = data[9]; document.getElementById('val-frame-radius').value = data[10]; + document.getElementById('val-frame-color').value = data[11]; document.getElementById('val-svg-color').value = data[12]; + document.getElementById('toggle-hide-labels').checked = data[13] === '1'; document.getElementById('val-title-size').value = data[14]; + document.getElementById('val-title-spacing').value = data[15]; document.getElementById('toggle-list-frame').checked = data[16] === '1'; + document.getElementById('val-icon-size').value = data[17]; document.getElementById('val-icon-spacing').value = data[18]; + document.getElementById('val-list-bg-opacity').value = data[19]; document.getElementById('val-frame-bg-opacity').value = data[20]; + document.getElementById('val-media-width').value = data[21]; document.getElementById('val-media-bg-opacity').value = data[22]; + document.getElementById('val-media-btn-size').value = data[23]; document.getElementById('val-media-bg-color').value = data[24]; + document.getElementById('val-media-btn-color').value = data[25]; document.getElementById('val-media-svg-color').value = data[26]; + document.getElementById('toggle-glass').checked = data[27] === '1'; document.getElementById('val-popup-anim').value = data[28]; + + document.querySelectorAll('.shadow-switch').forEach(c => { + c.checked = false; const drw = document.getElementById(`drawer-${c.value}`); if (drw) drw.classList.remove('active'); + }); + + if (data[29]) { + const shadows = data[29].split('~'); + shadows.forEach(sh => { + const p = sh.split('*'); + if (p.length > 1) { + const sId = p[0]; const shadowSwitch = document.querySelector(`input[name="active_shadow"][value="${sId}"]`); + if (shadowSwitch) { + shadowSwitch.checked = true; const drw = document.getElementById(`drawer-${sId}`); + if (drw) { + drw.classList.add('active'); + drw.querySelector('.s-x').value = p[1]; drw.querySelector('.s-y').value = p[2]; + drw.querySelector('.s-b').value = p[3]; drw.querySelector('.s-s').value = p[4]; + drw.querySelector('.s-c').value = p[5]; if (p[6]) drw.querySelector('.s-o').value = p[6]; + } } } - } - if (config.length >= 17) { - setC('toggle-hide-labels', config[14]); - setV('val-title-size', config[15]); - setV('val-title-spacing', config[16]); - } - if (config.length >= 18) setC('toggle-list-frame', config[17]); - if (config.length >= 20) { - setV('val-icon-size', config[18]); - setV('val-icon-spacing', config[19]); - } - setV('val-list-bg-opacity', config.length >= 21 ? config[20] : 10); - setV('val-frame-bg-opacity', config.length >= 22 ? config[21] : 100); - - if (config.length >= 28) { - setV('val-media-width', config[22]); - setV('val-media-bg-opacity', config[23]); - setV('val-media-btn-size', config[24]); - setV('val-media-bg-color', config[25]); - setV('val-media-btn-color', config[26]); - setV('val-media-svg-color', config[27]); - } - } else { - if (config.bgMain) { setV('val-bg-main', config.bgMain); root.style.setProperty('--bg-main', config.bgMain); } - if (config.textColor) setV('val-text-color', config.textColor); - if (config.layoutMode) setLayoutMode(config.layoutMode); - if (config.listBg) setV('val-list-bg', config.listBg); - if (config.listText) setV('val-list-text', config.listText); - if (config.listSvg) setV('val-list-svg', config.listSvg); - if (config.listBgOpacity) setV('val-list-bg-opacity', config.listBgOpacity); - if (config.listFrame !== undefined) setC('toggle-list-frame', config.listFrame); - - if (config.themeFrame) { - setV('val-theme-frame', config.themeFrame); - syncThemePickerVisuals(config.themeFrame); - } - if (config.frameSize) setV('val-frame-size', config.frameSize); - if (config.svgSize) setV('val-svg-size', config.svgSize); - if (config.svgOpacity) setV('val-svg-opacity', config.svgOpacity); - if (config.frameRadius) setV('val-frame-radius', config.frameRadius); - if (config.frameColor) setV('val-frame-color', config.frameColor); - if (config.frameBgOpacity) setV('val-frame-bg-opacity', config.frameBgOpacity); - if (config.svgColor) setV('val-svg-color', config.svgColor); - - if (config.hideLabels !== undefined) setC('toggle-hide-labels', config.hideLabels); - if (config.titleSize) setV('val-title-size', config.titleSize); - if (config.titleSpacing) setV('val-title-spacing', config.titleSpacing); - if (config.iconSize) setV('val-icon-size', config.iconSize); - if (config.iconSpacing) setV('val-icon-spacing', config.iconSpacing); - - if (config.mediaWidth) setV('val-media-width', config.mediaWidth); - if (config.mediaBgOpacity) setV('val-media-bg-opacity', config.mediaBgOpacity); - if (config.mediaBtnSize) setV('val-media-btn-size', config.mediaBtnSize); - if (config.mediaBgColor) setV('val-media-bg-color', config.mediaBgColor); - if (config.mediaBtnColor) setV('val-media-btn-color', config.mediaBtnColor); - if (config.mediaSvgColor) setV('val-media-svg-color', config.mediaSvgColor); - - if (config.glassMode !== undefined) setC('toggle-glass', config.glassMode); - if (config.popupAnim) { - setV('val-popup-anim', config.popupAnim); - if (drawerEl) drawerEl.className = 'settings-drawer ' + (config.popupAnim !== 'default' ? config.popupAnim : ''); - } - - if (config.shadowConfig) { - SHADOW_MODES.forEach(mode => { - const item = config.shadowConfig[mode.id]; - const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode.id}"]`); - const drawer = document.getElementById(`drawer-${mode.id}`); - if (item && checkbox && drawer) { - checkbox.checked = item.active; - if (item.active) drawer.classList.add('active'); - else drawer.classList.remove('active'); - drawer.querySelector('.s-x').value = item.x; - drawer.querySelector('.s-y').value = item.y; - drawer.querySelector('.s-b').value = item.b; - drawer.querySelector('.s-s').value = item.s; - drawer.querySelector('.s-c').value = item.c; - drawer.querySelector('.s-o').value = item.o; - } - }); - } + }); } - updateLiveVariables(); - } catch(e) { - console.error(e); - alert("Mã cấu hình không hợp lệ!"); - } + + if (data[30] !== undefined) document.getElementById('toggle-media-theme').checked = data[30] === '1'; + if (data[31] !== undefined) document.getElementById('toggle-theme-dot').checked = data[31] === '1'; else document.getElementById('toggle-theme-dot').checked = true; + if (data[32] !== undefined) document.getElementById('val-thumb-size').value = data[32]; + if (data[33] !== undefined) document.getElementById('val-thumb-color').value = data[33]; + if (data[34] !== undefined) document.getElementById('val-thumb-theme').value = data[34]; + + updateLiveVariables(true); + } catch(e) { console.error(e); alert("Lỗi đọc mã cấu hình! Vui lòng thử lại."); } } - const btnExport = document.getElementById('btn-export'); - if (btnExport) btnExport.onclick = () => openConfigModal('export'); + if (document.getElementById('btn-export')) { + document.getElementById('btn-export').addEventListener('click', (e) => { + e.preventDefault(); const code = packConfig(); + if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(code).then(() => { alert("Đã sao chép mã cấu hình thành công!"); }).catch(() => { prompt("Sao chép mã cấu hình bên dưới:", code); }); + else prompt("Sao chép mã cấu hình bên dưới:", code); + }); + } - const btnImport = document.getElementById('btn-import'); - if (btnImport) btnImport.onclick = () => openConfigModal('import'); + if (document.getElementById('btn-import')) { + document.getElementById('btn-import').addEventListener('click', (e) => { + e.preventDefault(); const code = prompt("📥 Dán mã cấu hình vào đây:"); + if (code) { unpackConfig(code); resetPresetToCustom(); } + }); + } - function updateLiveVariables() { - const g = id => { const el = document.getElementById(id); return el ? el.value : ''; }; - const c = id => { const el = document.getElementById(id); return el ? el.checked : false; }; + if (document.getElementById('btn-reset-default')) { + document.getElementById('btn-reset-default').addEventListener('click', (e) => { + e.preventDefault(); + if (confirm('⚠️ Khôi phục tất cả cài đặt về mặc định?\n\nThao tác này sẽ xóa toàn bộ tuỳ chỉnh, hình nền, theme hiện tại và KHÔNG THỂ hoàn tác.\n\nBạn có chắc chắn?')) { + try { + const keysToRemove = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith('sttv_')) keysToRemove.push(key); + } + keysToRemove.forEach(k => localStorage.removeItem(k)); + } catch(err) { console.warn(err); } + location.reload(); + } + }); + } - const bgMain = g('val-bg-main'); if (bgMain) root.style.setProperty('--bg-main', bgMain); - const textColor = g('val-text-color'); if (textColor) root.style.setProperty('--text-color', textColor); - const frameSize = g('val-frame-size'); if (frameSize) root.style.setProperty('--frame-size', frameSize + 'px'); - const svgSize = g('val-svg-size'); if (svgSize) root.style.setProperty('--svg-size', svgSize + 'px'); - const svgColor = g('val-svg-color'); if (svgColor) root.style.setProperty('--svg-color', svgColor); - const svgOpacity = g('val-svg-opacity'); if (svgOpacity) root.style.setProperty('--svg-opacity', svgOpacity / 100); + function updateLiveVariables(saveNow = false) { + root.style.setProperty('--bg-main', document.getElementById('val-bg-main').value); + root.style.setProperty('--text-color', document.getElementById('val-text-color').value); + root.style.setProperty('--frame-size', document.getElementById('val-frame-size').value + 'px'); + root.style.setProperty('--svg-size', document.getElementById('val-svg-size').value + 'px'); + root.style.setProperty('--svg-color', document.getElementById('val-svg-color').value); + root.style.setProperty('--svg-opacity', document.getElementById('val-svg-opacity').value / 100); - const rawRadius = g('val-frame-radius'); - if (rawRadius !== '') { - root.style.setProperty('--frame-border-radius', rawRadius + '%'); - root.style.setProperty('--list-border-radius', rawRadius + 'px'); - } - - const titleSize = g('val-title-size'); if (titleSize) root.style.setProperty('--title-size', titleSize + 'px'); - const titleSpacing = g('val-title-spacing'); if (titleSpacing) root.style.setProperty('--title-spacing', titleSpacing + 'px'); - const iconSize = g('val-icon-size'); if (iconSize) root.style.setProperty('--icon-font-size', iconSize + 'px'); - const iconSpacing = g('val-icon-spacing'); if (iconSpacing) root.style.setProperty('--icon-spacing', iconSpacing + 'px'); - root.style.setProperty('--label-display', c('toggle-hide-labels') ? 'none' : 'block'); + const rawRadius = document.getElementById('val-frame-radius').value; + root.style.setProperty('--frame-border-radius', rawRadius + '%'); + root.style.setProperty('--list-border-radius', rawRadius + 'px'); + + root.style.setProperty('--title-size', document.getElementById('val-title-size').value + 'px'); + root.style.setProperty('--title-spacing', document.getElementById('val-title-spacing').value + 'px'); + root.style.setProperty('--icon-font-size', document.getElementById('val-icon-size').value + 'px'); + root.style.setProperty('--icon-spacing', document.getElementById('val-icon-spacing').value + 'px'); + root.style.setProperty('--label-display', document.getElementById('toggle-hide-labels').checked ? 'none' : 'block'); - const currentLayout = safeGetItem('sttv_layoutMode') || 'grid'; - if (btnList && btnGrid) { - if (currentLayout === 'list') { btnList.classList.add('active'); btnGrid.classList.remove('active'); } - else { btnGrid.classList.add('active'); btnList.classList.remove('active'); } - } - - const isListFrame = c('toggle-list-frame'); - if (mainContainer) { - if (isListFrame) mainContainer.classList.add('list-frame-active'); - else mainContainer.classList.remove('list-frame-active'); - } - - const listBgHex = g('val-list-bg'); - const listBgOpacity = g('val-list-bg-opacity'); - if (listBgHex) root.style.setProperty('--list-bg-color', listBgHex); - if (listBgHex && listBgOpacity) root.style.setProperty('--list-bg-rgba', hexToRgba(listBgHex, listBgOpacity)); - const listText = g('val-list-text'); if (listText) root.style.setProperty('--list-text-color', listText); - const listSvg = g('val-list-svg'); if (listSvg) root.style.setProperty('--list-svg-color', listSvg); - - const frameSelect = g('val-theme-frame'); - const frameColorHex = g('val-frame-color'); - const frameColorOpacity = g('val-frame-bg-opacity'); - const frameColorRgba = hexToRgba(frameColorHex, frameColorOpacity); + const currentLayout = localStorage.getItem('sttv_layoutMode') || 'grid'; + const btnList = document.getElementById('btn-layout-list'); const btnGrid = document.getElementById('btn-layout-grid'); + if (currentLayout === 'list') { btnList.classList.add('active'); btnGrid.classList.remove('active'); mainContainer.classList.add('list-mode'); mainContainer.classList.remove('grid-mode'); } + else { btnGrid.classList.add('active'); btnList.classList.remove('active'); mainContainer.classList.add('grid-mode'); mainContainer.classList.remove('list-mode'); } + + const isListFrame = document.getElementById('toggle-list-frame').checked; + if (isListFrame) mainContainer.classList.add('list-frame-active'); else mainContainer.classList.remove('list-frame-active'); + + const listBgHex = document.getElementById('val-list-bg').value; + const listBgOpacity = document.getElementById('val-list-bg-opacity').value; + root.style.setProperty('--list-bg-color', listBgHex); + root.style.setProperty('--list-bg-rgba', hexToRgba(listBgHex, listBgOpacity)); + root.style.setProperty('--list-text-color', document.getElementById('val-list-text').value); + root.style.setProperty('--list-svg-color', document.getElementById('val-list-svg').value); + + const frameSelect = document.getElementById('val-theme-frame').value; + const frameColorRgba = hexToRgba(document.getElementById('val-frame-color').value, document.getElementById('val-frame-bg-opacity').value); + if (frameSelect === 'none') { root.style.setProperty('--frame-bg', 'none'); root.style.setProperty('--frame-bg-color', frameColorRgba); } + else { root.style.setProperty('--frame-bg', `url('../${frameSelect}')`); root.style.setProperty('--frame-bg-color', 'transparent'); } - if (frameSelect === 'none' || !frameSelect) { - root.style.setProperty('--frame-bg', 'none'); - root.style.setProperty('--frame-bg-color', frameColorRgba); - } else { - root.style.setProperty('--frame-bg', `url('../${frameSelect}')`); - root.style.setProperty('--frame-bg-color', 'transparent'); - } + if (document.getElementById('toggle-glass').checked) mainContainer.classList.add('glass-active'); else mainContainer.classList.remove('glass-active'); + + root.style.setProperty('--media-bg-rgb', hexToRgb(document.getElementById('val-media-bg-color').value)); + root.style.setProperty('--media-bg-opacity', document.getElementById('val-media-bg-opacity').value / 100); + root.style.setProperty('--media-width', document.getElementById('val-media-width').value + '%'); + root.style.setProperty('--media-btn-color', document.getElementById('val-media-btn-color').value); + root.style.setProperty('--media-svg-color', document.getElementById('val-media-svg-color').value); + const mediaBtnSize = document.getElementById('val-media.id}-btn"]-size`); +').value; + root.style.setProperty('--media-btn-size', mediaBtnSize + 'px'); + root.style.setProperty('--media-btn-play', (parseInt(mediaBtnSize) + 15) + 'px'); + + root.style.setProperty('--thumb-size', document.getElementById('val-thumb-size'). value + 'px'); + root.style.setProperty('--thumb-color', document.getElementById('val-thumb-color').value); + const thumbThemeSelect = document.getElementById('val-thumb-theme').value; + if (thumbThemeSelect === 'none') root.style.setProperty('--thumb-bg-image', "url('../Theme/icon-dot.png')"); + else root.style.setProperty('--thumb-bg-image', `url('../${thumbThemeSelect}')`); + + const toggleMediaTheme = document.getElementById('toggle-media-theme'); + const toggleThemeDot = document.getElementById('toggle-theme-dot'); + const btnThumbThemeEl = document.getElementById('btn-thumb-theme'); + const thumbColorItem = document.getElementById('val-thumb-color').parentElement; + const mediaWidget = document.querySelector('.media-player-widget'); - const glassMode = c('toggle-glass'); - if (mainContainer) { - if (glassMode) mainContainer.classList.add('glass-active'); - else mainContainer.classList.remove('glass-active'); - } - - const mediaBgColor = g('val-media-bg-color'); - if (mediaBgColor) root.style.setProperty('--media-bg-rgb', hexToRgb(mediaBgColor)); - const mediaBgOpacity = g('val-media-bg-opacity'); if (mediaBgOpacity) root.style.setProperty('--media-bg-opacity', mediaBgOpacity / 100); - const mediaWidth = g('val-media-width'); if (mediaWidth) root.style.setProperty('--media-width', mediaWidth + '%'); - const mediaBtnColor = g('val-media-btn-color'); if (mediaBtnColor) root.style.setProperty('--media-btn-color', mediaBtnColor); - const mediaSvgColor = g('val-media-svg-color'); if (mediaSvgColor) root.style.setProperty('--media-svg-color', mediaSvgColor); - const mediaBtnSize = g('val-media-btn-size'); - if (mediaBtnSize) { - root.style.setProperty('--media-btn-size', mediaBtnSize + 'px'); - root.style.setProperty('--media-btn-play', (parseInt(mediaBtnSize) + 15) + 'px'); + if (mediaWidget) { + if (toggleMediaTheme && toggleMedia constTheme.checked) mediaWidget.classList.add('theme-synced'); + else mediaWidget.classList.remove('theme-synced'); + + if (toggleThemeDot && toggleThemeDot.checked) { + mediaWidget.classList.remove('pure-css-dots'); + if(btnThumbThemeEl) btnThumbThemeEl.classList.remove('disabled'); + if(thumbColorItem) { thumbColorItem.style.opacity = '0.5'; thumbColorItem.style.pointerEvents = 'none'; } + } else { + mediaWidget.classList.add('pure-css-dots'); + if(btnThumbThemeEl) btnThumbThemeEl.classList.add('disabled'); + if(thumbColorItem) { thumbColorItem.style.opacity = '1'; thumbColorItem.style.pointerEvents = 'auto'; } + } } updateShadow(); - saveSettingsToLocal(); + if (saveNow) saveSettingsToLocal(); } function loadSettingsFromLocal() { const safeSet = (id, key, fallback, isCheck = false) => { - const el = document.getElementById(id); - if (!el) return; - const val = safeGetItem('sttv_' + key); + const el = document.getElementById(id); if (!el) return; + const val = localStorage.getItem('sttv_' + key); if (isCheck) el.checked = val === 'true' ? true : (val === null ? fallback : false); else el.value = val !== null ? val : fallback; }; - safeSet('val-bg-main', 'bgMain', '#121b22'); - safeSet('val-text-color', 'textColor', '#ffffff'); - safeSet('val-list-bg', 'listBg', '#1a1a1a'); - safeSet('val-list-bg-opacity', 'listBgOpacity', '10'); - safeSet('val-list-text', 'listText', '#ffffff'); - safeSet('val-list-svg', 'listSvg', '#ffffff'); + safeSet('val-bg-main', 'bgMain', '#121b22'); safeSet('val-text-color', 'textColor', '#ffffff'); + safeSet('val-list-bg', 'listBg', '#1a1a1a'); safeSet('val-list-bg-opacity', 'listBgOpacity', '10'); + safeSet('val-list-text', 'listText', '#ffffff'); safeSet('val-list-svg', 'listSvg', '#ffffff'); + + if (document.getElementById('val-theme-frame')) document.getElementById('val-theme-frame').value = localStorage.getItem('sttv_themeFrame') || 'none'; + presetSelect.value = localStorage.getItem('sttv_activePreset') || 'none'; - const savedFrame = safeGetItem('sttv_themeFrame') || 'none'; - const frameEl = document.getElementById('val-theme-frame'); - if (frameEl) frameEl.value = savedFrame; - syncThemePickerVisuals(savedFrame); - - const savedPreset = safeGetItem('sttv_activePreset') || 'none'; - if (presetSelect) presetSelect.value = savedPreset; - - const savedAnim = safeGetItem('sttv_popupAnim') || 'default'; - if (popupAnimSelect) popupAnimSelect.value = savedAnim; - if (savedAnim !== 'default' && drawerEl) drawerEl.classList.add(savedAnim); - - safeSet('val-frame-size', 'frameSize', '60'); - safeSet('val-svg-size', 'svgSize', '28'); - safeSet('val-svg-opacity', 'svgOpacity', '100'); - safeSet('val-svg-color', 'svgColor', '#ffffff'); - safeSet('val-frame-radius', 'frameRadius', '22'); - safeSet('val-frame-color', 'frameColor', '#000000'); + const savedAnim = localStorage.getItem('sttv_popupAnim') || 'default'; + if (document.getElementById('val-popup-anim')) document.getElementById('val-popup-anim').value = savedAnim; + if (savedAnim !== 'default') drawerEl.classList.add(savedAnim); + + safeSet('val-frame-size', 'frameSize', '60'); safeSet('val-svg-size', 'svgSize', '28'); + safeSet('val-svg-opacity', 'svgOpacity', '100'); safeSet('val-svg-color', 'svgColor', '#ffffff'); + safeSet('val-frame-radius', 'frameRadius', '22'); safeSet('val-frame-color', 'frameColor', '#000000'); safeSet('val-frame-bg-opacity', 'frameBgOpacity', '100'); - safeSet('toggle-hide-labels', 'hideLabels', false, true); - safeSet('toggle-list-frame', 'listFrame', false, true); - safeSet('val-title-size', 'titleSize', '22'); - safeSet('val-title-spacing', 'titleSpacing', '0.5'); - safeSet('val-icon-size', 'iconSize', '14'); - safeSet('val-icon-spacing', 'iconSpacing', '0'); - safeSet('toggle-glass', 'glassMode', false, true); - safeSet('toggle-audio', 'audioFeedback', false, true); + safeSet('toggle-hide-labels', 'hideLabels', false, true); safeSet('toggle-list-frame', 'listFrame', false, true); + safeSet('val-title-size', 'titleSize', '22'); safeSet('val-title-spacing', 'titleSpacing', '0.5'); + safeSet('val-icon-size', 'iconSize', '14'); safeSet('val-icon-spacing', 'iconSpacing', '0'); + safeSet('toggle-glass', 'glassMode', false, true); safeSet('toggle-audio', 'audioFeedback', false, true); safeSet('toggle-parallax', 'parallax', false, true); - safeSet('val-media-width', 'mediaWidth', '90'); - safeSet('val-media-bg-opacity', 'mediaBgOpacity', '3'); - safeSet('val-media-btn-size', 'mediaBtnSize', '50'); - safeSet('val-media-bg-color', 'mediaBgColor', '#ffffff'); - safeSet('val-media-btn-color', 'mediaBtnColor', '#ffffff'); - safeSet('val-media-svg-color', 'mediaSvgColor', '#ffffff'); + safeSet('val-media-width', 'mediaWidth', '90'); safeSet('val-media-bg-opacity', 'mediaBgOpacity', '3'); + safeSet('val-media-btn-size', 'mediaBtnSize', '50'); safeSet('val-media-bg-color', 'mediaBgColor', '#ffffff'); + safeSet('val-media-btn-color', 'mediaBtnColor', '#ffffff'); safeSet('val-media-svg-color', 'mediaSvgColor', '#ffffff'); + safeSet('toggle-media-theme', 'mediaThemeSync', false, true); + + safeSet('toggle-theme-dot', 'themeDot', true, true); + safeSet('val-thumb-size', 'thumbSize', '25'); + safeSet('val-thumb-color', 'thumbColor', '#ffffff'); + if (document.getElementById('val-thumb-theme')) document.getElementById('val-thumb-theme').value = localStorage.getItem('sttv_thumbTheme') || 'none'; - const savedShadowConfig = safeGetItem('sttv_shadowConfig'); + const savedShadowConfig = localStorage.getItem('sttv_shadowConfig'); if (savedShadowConfig) { try { const shadowState = JSON.parse(savedShadowConfig); SHADOW_MODES.forEach(mode => { if (shadowState[mode.id]) { const item = shadowState[mode.id]; - const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode.id}"]`); - const drawer = document.getElementById(`drawer-${mode.id}`); + const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode drawer = document.getElementById(`drawer-${mode.id}`); if (checkbox && drawer) { checkbox.checked = item.active; - if (item.active) drawer.classList.add('active'); - else drawer.classList.remove('active'); - drawer.querySelector('.s-x').value = item.x; - drawer.querySelector('.s-y').value = item.y; - drawer.querySelector('.s-b').value = item.b; - drawer.querySelector('.s-s').value = item.s; - drawer.querySelector('.s-c').value = item.c; - drawer.querySelector('.s-o').value = item.o; + if (item.active) drawer.classList.add('active'); else drawer.classList.remove('active'); + drawer.querySelector('.s-x').value = item.x; drawer.querySelector('.s-y').value = item.y; + drawer.querySelector('.s-b').value = item.b; drawer.querySelector('.s-s').value = item.s; + drawer.querySelector('.s-c').value = item.c; drawer.querySelector('.s-o').value = item.o; } } }); - } catch(e) { console.error("Lỗi đọc cấu hình shadow:", e); } + } catch(e) {} } - const initLayout = safeGetItem('sttv_layoutMode') || 'grid'; - if (btnList && btnGrid) { - if (initLayout === 'list') { btnList.classList.add('active'); btnGrid.classList.remove('active'); } - else { btnGrid.classList.add('active'); btnList.classList.remove('active'); } - } + const initLayout = localStorage.getItem('sttv_layoutMode') || 'grid'; + if (initLayout === 'list') { document.getElementById('btn-layout-list').classList.add('active'); document.getElementById('btn-layout-grid').classList.remove('active'); } + else { document.getElementById('btn-layout-grid').classList.add('active'); document.getElementById('btn-layout-list').classList.remove('active'); } - const savedBg = safeGetItem('sttv_customBgImage'); + const savedBg = localStorage.getItem('sttv_customBgImage'); if (savedBg) root.style.setProperty('--bg-image', `url('${savedBg}')`); } loadSettingsFromLocal(); - updateLiveVariables(); + updateLiveVariables(true); - document.addEventListener("visibilitychange", function() { - if (document.visibilityState === 'hidden') saveSettingsToLocal(); - }); + document.addEventListener("visibilitychange", () => { if (document.visibilityState === 'hidden') saveSettingsToLocal(); }); window.addEventListener("beforeunload", saveSettingsToLocal); - window.addEventListener("pagehide", saveSettingsToLocal); }); \ No newline at end of file From dd251804b7dd0d49e47d5bee57d9501ea183e675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:27:20 +0700 Subject: [PATCH 04/23] Update style.css --- CSS/style.css | 395 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 294 insertions(+), 101 deletions(-) diff --git a/CSS/style.css b/CSS/style.css index ebe0dcc..fda8b1f 100644 --- a/CSS/style.css +++ b/CSS/style.css @@ -13,15 +13,17 @@ --media-bg-rgb: 255, 255, 255; --media-bg-opacity: 0.03; --media-width: 90%; --media-btn-size: 50px; --media-btn-play: 65px; --media-btn-color: rgba(255, 255, 255, 0.05); --media-svg-color: #ffffff; + --thumb-size: 25px; --thumb-color: #ffffff; --thumb-bg-image: url('../Theme/icon-dot.png'); --bg-image: none; } * { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, sans-serif; -webkit-tap-highlight-color: transparent; } a { text-decoration: none; color: inherit; } -body { color: var(--text-color); min-height: 100vh; overflow-x: hidden; } +html, body { width: 100%; height: 100%; margin: 0; padding: 0; } +body { color: var(--text-color); min-height: 100vh; overflow-x: hidden; overscroll-behavior: none; } body::before { - content: ""; position: fixed; top: 0; left: 0; right: 0; bottom: 0; + content: ""; position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: 100vw; height: 100vh; background-color: var(--bg-main); background-image: var(--bg-image); background-size: cover; background-position: center; background-repeat: no-repeat; z-index: -1; transition: background-color 0.3s ease; } @@ -31,56 +33,49 @@ body.standard-mode .dev-only { display: none !important; } .app-header { text-align: center; padding: 60px 0 20px 0; font-size: var(--title-size); font-weight: bold; letter-spacing: var(--title-spacing); text-shadow: none; transition: font-size 0.2s, letter-spacing 0.2s; } .label { font-size: var(--icon-font-size, 14px); font-weight: 500; letter-spacing: var(--icon-spacing, 0px); text-align: center; text-shadow: none; display: var(--label-display); transition: font-size 0.2s, letter-spacing 0.2s; } +/* ============================================ + INTRO SCREEN + ============================================ */ #intro-screen { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 99999; display: flex; justify-content: center; align-items: center; pointer-events: none; } -.intro-half { position: absolute; top: 0; width: 50%; height: 100%; background: #000000; transition: transform 1.5s cubic-bezier(0.77, 0, 0.175, 1); z-index: 1; } +.intro-half { position: absolute; top: 0; width: 50%; height: 100%; background: #000000; transition: transform 0.5s cubic-bezier(0.77, 0, 0.175, 1); z-index: 1; } .intro-left { left: 0; } .intro-right { right: 0; } - .intro-content { position: relative; z-index: 10; display: flex; flex-direction: column; align-items: center; transition: opacity 0.5s ease, transform 0.5s ease; } .apple-logo-wrapper { color: #ffffff; filter: drop-shadow(0 0 12px #007aff) drop-shadow(0 0 25px #007aff); animation: pulseGlow 1.5s infinite alternate; } @keyframes pulseGlow { 0% { filter: drop-shadow(0 0 8px #007aff); transform: scale(1); } 100% { filter: drop-shadow(0 0 22px #007aff) drop-shadow(0 0 35px #0051a8); transform: scale(1.05); } } - -.intro-title-wrapper { margin-top: 18px; opacity: 0; animation: fadeInDelay 0.5s forwards 1.5s; } +.intro-title-wrapper { margin-top: 18px; opacity: 0; animation: fadeInDelay 0.5s forwards 0.8s; } @keyframes fadeInDelay { to { opacity: 1; } } - .intro-title-container { font-size: 24px; font-weight: bold; color: #007aff; display: flex; } .intro-title-container span { display: inline-block; } - .anim-wave span { animation: waveAnim 1.2s infinite ease-in-out; } @keyframes waveAnim { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); color: #66b2ff; } } - .anim-bounce span { animation: bounceAnim 1s infinite ease-in-out alternate; } @keyframes bounceAnim { 0% { transform: translateY(0); } 100% { transform: translateY(-10px) scale(1.1); color: #33ccff; } } - .anim-flip span { animation: flipAnim 2s infinite ease-in-out; perspective: 1000px; } @keyframes flipAnim { 0%, 100% { transform: rotateY(0deg); } 50% { transform: rotateY(180deg) scale(1.1); color: #00f2fe; } } - -.intro-title-container span:nth-child(1) { animation-delay: 0.0s; } .intro-title-container span:nth-child(2) { animation-delay: 0.1s; } -.intro-title-container span:nth-child(3) { animation-delay: 0.2s; } .intro-title-container span:nth-child(4) { animation-delay: 0.3s; } -.intro-title-container span:nth-child(5) { animation-delay: 0.4s; } .intro-title-container span:nth-child(6) { animation-delay: 0.5s; } -.intro-title-container span:nth-child(7) { animation-delay: 0.6s; } .intro-title-container span:nth-child(8) { animation-delay: 0.7s; } -.intro-title-container span:nth-child(9) { animation-delay: 0.8s; } .intro-title-container span:nth-child(10) { animation-delay: 0.9s; } -.intro-title-container span:nth-child(11) { animation-delay: 1.0s; } .intro-title-container span:nth-child(12) { animation-delay: 1.1s; } -.intro-title-container span:nth-child(13) { animation-delay: 1.2s; } - #intro-screen.dismiss .intro-content { opacity: 0; transform: scale(0.8); transition-duration: 0.5s; } #intro-screen.dismiss .intro-left { transform: translateX(-100%); } #intro-screen.dismiss .intro-right { transform: translateX(100%); } -.app-container { transition: transform 1s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 1s ease; } +.app-container { transition: transform 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.8s ease; } .app-container.intro-zoom { transform: scale(0.9); opacity: 0; } -.slider-item { position: relative; display: flex; align-items: center; margin-bottom: 8px; width: 100%; } -.slider-item > div { flex: 1; display: flex; align-items: center; } +/* ============================================ + SLIDER ITEM & TOOLTIP + ============================================ */ +.slider-item { position: relative; display: flex; align-items: center; margin-bottom: 8px; width: 100%; transition: opacity 0.3s ease; } .slider-item input[type=range] { flex: 1; width: 100% !important; } -.slider-label { width: 95px; font-size: 12px; opacity: 0.8; flex-shrink: 0; } -.slider-tooltip { position: absolute; top: -28px; background: #007aff; color: #ffffff; font-size: 11px; font-weight: bold; padding: 4px 8px; border-radius: 8px; opacity: 0; transform: translateY(5px); transition: opacity 0.2s, transform 0.2s, left 0.1s ease-out; pointer-events: none; z-index: 50; white-space: nowrap; box-shadow: 0 2px 6px rgba(0,0,0,0.3); } -.slider-tooltip.show { opacity: 1; transform: translateY(0); } +.slider-label { width: 105px; font-size: 12px; opacity: 0.8; flex-shrink: 0; } +.slider-tooltip { position: fixed; background: #007aff; color: #ffffff; font-size: 11px; font-weight: bold; padding: 4px 8px; border-radius: 8px; opacity: 0; transform: translateX(-50%) translateY(5px); transition: opacity 0.15s ease, transform 0.15s ease; pointer-events: none; z-index: 999999; white-space: nowrap; box-shadow: 0 2px 8px rgba(0,0,0,0.4); } +.slider-tooltip.show { opacity: 1; transform: translateX(-50%) translateY(0); } .badge-dot { display: inline-block; width: 8px; height: 8px; background-color: #ff3b30; border-radius: 50%; margin-left: 5px; vertical-align: middle; box-shadow: 0 0 6px #ff3b30; animation: badgePulse 1.5s infinite; } @keyframes badgePulse { 0% { opacity: 0.5; } 100% { opacity: 1; transform: scale(1.2); } } .badge-dot.hidden { display: none !important; } +/* ============================================ + GRID LAYOUT + ============================================ */ .grid-layout { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; padding: 20px; max-width: 600px; margin: 0 auto; position: relative; } .glass-btn { display: flex; flex-direction: column; align-items: center; gap: 8px; text-decoration: none; color: var(--text-color); user-select: none; position: relative; } .icon-box { @@ -88,11 +83,11 @@ body.standard-mode .dev-only { display: none !important; } background-size: cover; background-position: center; background-repeat: no-repeat; border-radius: var(--frame-border-radius); display: flex; justify-content: center; align-items: center; box-shadow: var(--btn-shadow); transition: transform 0.1s ease, box-shadow 0.2s ease; transform: perspective(500px) translate3d(var(--tilt-x), var(--tilt-y), 0); + will-change: transform; } .icon-box svg { width: var(--svg-size); height: var(--svg-size); fill: var(--svg-color); color: var(--svg-color); opacity: var(--svg-opacity); position: relative; z-index: 2; transform: perspective(500px) translate3d(calc(var(--tilt-x) * 1.5), calc(var(--tilt-y) * 1.5), 10px); } .glass-btn:active .icon-box { transform: scale(0.92); } .glass-active .icon-box { background: rgba(255,255,255,0.15); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); border: 1px solid rgba(255,255,255,0.2); } -.glass-active.list-mode .glass-btn { background: rgba(0,0,0,0.3); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid rgba(255,255,255,0.1); } @keyframes pulseSwap { 0% { box-shadow: 0 0 0 0 rgba(0, 122, 255, 0.7); } 70% { box-shadow: 0 0 0 15px rgba(0, 122, 255, 0); } 100% { box-shadow: 0 0 0 0 rgba(0, 122, 255, 0); } } .edit-mode .glass-btn { pointer-events: auto; cursor: pointer; } @@ -100,104 +95,116 @@ body.standard-mode .dev-only { display: none !important; } .edit-mode .glass-btn.selected-swap::after { content: "🔄"; position: absolute; top: -5px; right: -5px; font-size: 18px; background: #007aff; border-radius: 50%; padding: 2px; } .edit-mode .grid-layout { background: rgba(255,255,255,0.05); border: 2px dashed rgba(255,255,255,0.3); border-radius: 20px; } -.app-container.list-mode .grid-layout { grid-template-columns: 1fr; gap: 12px; } -.app-container.list-mode .glass-btn { flex-direction: row; justify-content: flex-start; align-items: center; gap: 15px; background: var(--list-bg-rgba); padding: 12px 18px; border-radius: var(--list-border-radius); color: var(--list-text-color); box-shadow: var(--btn-shadow); } +/* ============================================ + LIST MODE + ============================================ */ +.app-container.list-mode .grid-layout { grid-template-columns: 1fr !important; gap: 12px; } +.app-container.list-mode .glass-btn { + flex-direction: row; justify-content: flex-start; align-items: center; gap: 15px; + background: var(--list-bg-rgba); padding: 12px 18px; border-radius: var(--list-border-radius); + color: var(--list-text-color); + box-shadow: var(--btn-shadow); +} .app-container.list-mode:not(.list-frame-active) .icon-box { background: transparent !important; box-shadow: none !important; width: var(--svg-size); height: var(--svg-size); transform: none; border-radius: 0; } .app-container.list-mode:not(.list-frame-active) .icon-box svg { fill: var(--list-svg-color); color: var(--list-svg-color); width: 100%; height: 100%; transform: none; opacity: var(--svg-opacity); } .app-container.list-mode .label { text-align: left; } -.app-container.list-mode.list-frame-active .glass-btn { background: var(--list-bg-rgba); box-shadow: none; } +.app-container.list-mode.list-frame-active .glass-btn { background: var(--list-bg-rgba); box-shadow: var(--btn-shadow); } .app-container.list-mode.list-frame-active .icon-box { width: var(--frame-size); height: var(--frame-size); min-width: var(--frame-size); background-image: var(--frame-bg) !important; background-color: var(--frame-bg-color) !important; background-size: cover; background-position: center; background-repeat: no-repeat; border-radius: var(--frame-border-radius); box-shadow: var(--btn-shadow) !important; transform: perspective(500px) translate3d(var(--tilt-x), var(--tilt-y), 0); display: flex; justify-content: center; align-items: center; } .app-container.list-mode.list-frame-active .icon-box svg { fill: var(--svg-color); color: var(--svg-color); width: var(--svg-size); height: var(--svg-size); opacity: var(--svg-opacity); } +/* ============================================ + MEDIA WIDGET + ============================================ */ .media-player-widget { margin: 10px auto 30px auto; width: var(--media-width); max-width: 400px; padding: 20px; border-radius: 24px; display: flex; flex-direction: column; gap: 22px; background: rgba(var(--media-bg-rgb), var(--media-bg-opacity)); box-shadow: var(--btn-shadow); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); } .media-controls { display: flex; align-items: center; justify-content: center; gap: 30px; } .media-btn { border: none; background: var(--media-btn-color); color: var(--media-svg-color); border-radius: 50%; width: var(--media-btn-size); height: var(--media-btn-size); display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: var(--btn-shadow); transition: all 0.15s ease; } .media-btn.play-main { width: var(--media-btn-play); height: var(--media-btn-play); } .media-btn:active { box-shadow: inset 0 0 10px rgba(0,0,0,0.3); transform: scale(0.95); } +.media-player-widget.theme-synced .media-btn { background-image: var(--frame-bg) !important; background-color: var(--frame-bg-color) !important; background-size: cover; background-position: center; box-shadow: var(--btn-shadow) !important; } + .system-sliders { display: flex; flex-direction: column; gap: 15px; width: 100%; } .sys-slider-container { display: flex; align-items: center; gap: 12px; color: var(--media-svg-color); opacity: 0.8; } .sys-slider { -webkit-appearance: none; flex: 1; height: 8px !important; border-radius: 4px; background: rgba(0, 0, 0, 0.3) !important; box-shadow: inset 1px 1px 3px rgba(0,0,0,0.5), inset -1px -1px 3px rgba(255,255,255,0.1) !important; outline: none; } -.settings-fab { position: fixed; bottom: 25px; right: 25px; width: 45px; height: 45px; border-radius: 50%; background: transparent; border: none; box-shadow: none; z-index: 100; cursor: pointer; overflow: hidden; padding: 0; transition: transform 0.2s ease; } -.settings-fab img { width: 100%; height: 100%; object-fit: contain; } -.settings-fab:active { transform: scale(0.9); } - +/* ============================================ + SLIDER THUMBS + ============================================ */ input[type=range]::-webkit-slider-thumb, .sys-slider::-webkit-slider-thumb { - -webkit-appearance: none; height: 25px !important; width: 25px !important; - background-size: contain !important; background-position: center !important; - background-repeat: no-repeat !important; background-color: transparent !important; - border-radius: 0 !important; border: none !important; box-shadow: none !important; - filter: drop-shadow(0px 2px 4px rgba(0,0,0,0.4)); cursor: pointer; -} -input[type=range]::-webkit-slider-thumb { - -webkit-appearance: none !important; - height: 25px !important; - width: 25px !important; - background-image: url('../Theme/icon-dot.png') !important; - background-size: contain !important; - background-position: center !important; - background-repeat: no-repeat !important; - background-color: transparent !important; - border: none !important; - margin-top: -9.5px !important; - filter: drop-shadow(0px 2px 4px rgba(0,0,0,0.4)); - cursor: pointer; -} -.sys-slider::-webkit-slider-thumb { - -webkit-appearance: none !important; - height: 25px !important; - width: 25px !important; - background-image: url('../Theme/icon-control.png') !important; - background-size: contain !important; - background-position: center !important; - background-repeat: no-repeat !important; - background-color: transparent !important; - border: none !important; - margin-top: -8.5px !important; - filter: drop-shadow(0px 2px 4px rgba(0,0,0,0.4)); - cursor: pointer; + -webkit-appearance: none !important; appearance: none !important; + height: var(--thumb-size) !important; width: var(--thumb-size) !important; + background-image: var(--thumb-bg-image) !important; + background-size: contain !important; background-position: center !important; background-repeat: no-repeat !important; + background-color: transparent !important; border: none !important; + margin-top: calc((var(--thumb-size) / -2) + 3px) !important; + box-shadow: none !important; filter: drop-shadow(0px 2px 4px rgba(0,0,0,0.4)); cursor: pointer; } -input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 6px; background: rgba(255,255,255,0.2); border-radius: 3px; } +input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 6px; background: rgba(255,255,255,0.2); border-radius: 3px; box-shadow: none; } -.settings-drawer .theme-grid-picker { - display: grid; grid-template-columns: repeat(5, 1fr); grid-template-rows: repeat(2, 44px); - gap: 12px; margin-top: 10px; justify-items: center; align-items: center; overflow: hidden; +.media-player-widget.pure-css-dots .sys-slider::-webkit-slider-thumb { + background-image: none !important; + background-color: var(--thumb-color) !important; + box-shadow: var(--btn-shadow) !important; + border-radius: 50% !important; + opacity: var(--svg-opacity) !important; + filter: none !important; } -.macos-window .full-grid { - display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; - max-height: 280px; overflow-y: auto; padding: 5px; justify-items: center; align-items: center; +/* ============================================ + THEME DOT PLUS BUTTON + ============================================ */ +.btn-plus-theme { + width: 26px; height: 26px; border-radius: 50%; background: #007aff; color: white; border: none; + font-size: 18px; font-weight: 300; display: flex; align-items: center; justify-content: center; cursor: pointer; + transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0, 122, 255, 0.4); } - -.theme-chip { - width: calc(100% - 3px); max-width: 44px; aspect-ratio: 1; border-radius: 10px; - background: transparent; border: 2px solid transparent; - display: flex; align-items: center; justify-content: center; - cursor: pointer; transition: all 0.2s ease; padding: 2px; +.btn-plus-theme.disabled { + background: #8e8e93; box-shadow: none; opacity: 0.5; pointer-events: none; } +.btn-plus-theme span { margin-top: -2px; } + +/* ============================================ + FLOATING FAB BUTTONS + ============================================ */ +.settings-fab { position: fixed; bottom: 25px; right: 25px; width: 45px; height: 45px; border-radius: 50%; background: transparent; border: none; box-shadow: none; z-index: 100; cursor: pointer; overflow: hidden; padding: 0; transition: transform 0.2s ease; } +.settings-fab img { width: 100%; height: 100%; object-fit: contain; } +.settings-fab:active { transform: scale(0.9); } +.sort-fab { position: fixed; bottom: 35px; left: 50%; transform: translateX(-50%) scale(1); width: 56px; height: 56px; border-radius: 50%; border: none; background: #8e8e93; color: white; box-shadow: 0 4px 15px rgba(0,0,0,0.3); z-index: 1000; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } +.sort-fab.hidden { transform: translateX(-50%) scale(0) !important; opacity: 0; pointer-events: none; } +.sort-fab.active { background: #007aff; box-shadow: 0 4px 20px rgba(0, 122, 255, 0.5); transform: translateX(-50%) scale(1.1); } +.sort-fab svg { width: 30px; height: 30px; stroke-width: 3.5; } +.sort-fab:active { transform: translateX(-50%) scale(0.95); } + +/* ============================================ + THEME PICKER GRID + ============================================ */ +.settings-drawer .theme-grid-picker { display: grid; grid-template-columns: repeat(5, 1fr); grid-template-rows: repeat(2, 44px); gap: 12px; margin-top: 10px; justify-items: center; align-items: center; overflow: hidden; } +.macos-window .full-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; max-height: 280px; overflow-y: auto; padding: 5px; justify-items: center; align-items: center; } +.theme-chip { width: calc(100% - 3px); max-width: 44px; aspect-ratio: 1; border-radius: 10px; background: transparent; border: 2px solid transparent; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; padding: 2px; } .theme-chip img { width: 100%; height: 100%; object-fit: contain; pointer-events: none; border-radius: inherit; } .theme-chip.active { border-color: var(--accent-color); box-shadow: 0 0 12px rgba(0, 122, 255, 0.4); background: rgba(0, 122, 255, 0.1); } - -.theme-chip-more { - width: calc(100% - 3px); max-width: 44px; aspect-ratio: 1; border-radius: 50%; - background: #007aff; color: #ffffff; font-size: 26px; font-weight: 300; - display: flex; align-items: center; justify-content: center; cursor: pointer; - box-shadow: 0 2px 8px rgba(0, 122, 255, 0.4); transition: all 0.2s ease; -} +.theme-chip-more { width: calc(100% - 3px); max-width: 44px; aspect-ratio: 1; border-radius: 50%; background: #007aff; color: #ffffff; font-size: 26px; font-weight: 300; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 8px rgba(0, 122, 255, 0.4); transition: all 0.2s ease; } .theme-chip-more span { margin-top: -2px; } .theme-chip-more:active { transform: scale(0.9); } -.settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); opacity: 0; visibility: hidden; transition: opacity 0.3s ease; z-index: 998; } +/* ============================================ + DRAWER & OVERLAY + ============================================ */ +.settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); opacity: 0; visibility: hidden; transition: opacity 0.45s cubic-bezier(0.32, 0.72, 0, 1); z-index: 998; } .settings-overlay.open { opacity: 1; visibility: visible; } -.settings-drawer { position: fixed; top: calc(env(safe-area-inset-top) + 15px); right: -120%; width: 85%; max-width: 360px; max-height: 85vh; border-radius: 20px; background: rgba(30,30,30,0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); box-shadow: -5px 0 25px rgba(0,0,0,0.5); z-index: 999; transition: right 0.4s cubic-bezier(0.1, 0.7, 0.1, 1), background 0.3s ease, backdrop-filter 0.3s ease; display: flex; flex-direction: column; color: white; overflow: hidden; } +.settings-drawer { + position: fixed; top: calc(env(safe-area-inset-top) + 15px); right: -120%; width: 85%; max-width: 360px; max-height: 85vh; border-radius: 20px; + background: rgba(30,30,30,0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); box-shadow: -5px 0 25px rgba(0,0,0,0.5); + z-index: 999; transition: right 0.45s cubic-bezier(0.32, 0.72, 0, 1), background 0.3s ease; + will-change: right, opacity; display: flex; flex-direction: column; color: white; overflow: hidden; +} .settings-drawer.open { right: 15px; } .settings-drawer.adjusting { background: rgba(30, 30, 30, 0.1) !important; opacity: 0.15 !important; backdrop-filter: blur(0px) !important; -webkit-backdrop-filter: blur(0px) !important; box-shadow: none !important; } -.settings-header { padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(255,255,255,0.1); } -.settings-header button { background: rgba(255,255,255,0.2); border: none; color: white; width: 30px; height: 30px; border-radius: 50%; cursor: pointer; } + .settings-body { padding: 20px 20px 60px 20px; overflow-y: auto; flex: 1; } .setting-group { margin-bottom: 20px; display: flex; flex-direction: column; gap: 8px; } -.highlight-box { background: rgba(255,255,255,0.05); padding: 15px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1); } -.feature-toggles { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; font-size: 13px; } +.highlight5-box { background: rgba(255,255,255,px0.05); padding: 15px; border-radius:; + 12px; border: 1px solid rgba( 255,255,255,0 line.1); } +.feature-toggles { display-height: flex; flex-direction: column; gap: 10px; margin-top: 10px; font-size: 13px; } .feature-toggles label { display: flex; justify-content: space-between; align-items: center; } .section-title { margin: 20px 0 10px; font-size: 16px; color: var(--accent-color); border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 5px; } .slider-item input[type="color"] { height: 28px; padding: 0; border: none; background: none; border-radius: 5px; cursor: pointer; } @@ -211,21 +218,207 @@ input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 6px; bac .shadow-drawer { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; padding: 0 15px; background: rgba(0,0,0,0.1); } .shadow-drawer.active { max-height: 400px; padding: 15px; } select { width: 100%; padding: 10px; border-radius: 8px; background: rgba(0,0,0,0.3); color: white; border: 1px solid rgba(255,255,255,0.2); font-size: 14px; } -.info-btn { position: absolute; bottom: 20px; right: 20px; width: 28px; height: 28px; border-radius: 50%; background: #007aff; color: #ffffff; border: none; font-size: 15px; font-weight: bold; font-family: -apple-system, sans-serif; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 10; } -.info-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); opacity: 0; visibility: hidden; transition: opacity 0.3s ease; z-index: 2000; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); } +/* ============================================ + ⭐ SETTINGS HEADER MỚI - Nút X / Pill CÀI ĐẶT / Nút Info + ============================================ */ +.settings-header { + padding: 12px 15px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid rgba(255,255,255,0.08); + position: relative; + min-height: 58px; +} +.settings-header .header-btn { + background: rgba(255,255,255,0.12); + border: none; + color: white; + width: 34px; + height: 34px; + border-radius: 50%; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: bold; + font-family: -apple-system, sans-serif; + transition: transform 0.15s ease, background 0.2s ease; + padding: 0; + flex-shrink: 0; + z-index: 2; +} +.settings-header .header-btn:active { transform: scale(0.88); background: rgba(255,255,255,0.22); } +.settings-header .header-btn.info-header-btn { + background: linear-gradient(135deg, #007aff, #0055cc); + font-style: italic; + font-family: Georgia, serif; + box-shadow: 0 2px 10px rgba(0, 122, 255, 0.55), inset 0 1px 0 rgba(255,255,255,0.3); +} +.settings-header .header-btn.info-header-btn:active { + background: linear-gradient(135deg, #0066dd, #0044aa); +} +.settings-header .header-title-pill { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + background: linear-gradient(135deg, #007aff, #0055cc); + color: white; + padding: 8px 26px; + border-radius: 22px; + font-size: 13px; + font-weight: 800; + letter-spacing: 1.5px; + box-shadow: 0 3px 14px rgba(0, 122, 255, 0.5), inset 0 1px 0 rgba(255,255,255,0.3); + text-transform: uppercase; + white-space: nowrap; + pointer-events: none; + z-index: 1; +} + +/* ============================================ + ⭐ NÚT KHÔI PHỤC MẶC ĐỊNH + ============================================ */ +.config-actions-secondary { + display: flex; + margin-top: -12px; + margin-bottom: 20px; +} +.config-actions-secondary .action-btn { + background: rgba(255, 59, 48, 0.12); + border: 1px solid rgba(255, 59, 48, 0.28); + color: #ff6b60; + font-size: 12.5px; + font-weight: 600; + padding: 10px 8px; + transition: all 0.2s ease; +} +.config-actions-secondary .action-btn:active { + background: rgba(255, 59, 48, 0.28); + color: #ff8a80; + transform: scale(0.97); +} +.config-actions-secondary .action-btn.danger { width: 100%; } + +/* ============================================ + MACOS MODAL WINDOWS + ============================================ */ +.info-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); opacity: 0; visibility: hidden; transition: opacity 0.45s ease; z-index: 2000; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); } .info-overlay.show { opacity: 1; visibility: visible; } -.macos-window { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.9); width: 85%; max-width: 380px; background: #ffffff; border-radius: 16px; box-shadow: 0 20px 40px rgba(0,0,0,0.3); z-index: 2001; opacity: 0; visibility: hidden; transition: all 0.3s ease; color: #000000; overflow: hidden; } +.macos-window { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.9); width: 85%; max-width: 380px; background: #ffffff; border-radius: 16px; box-shadow: 0 20px 40px rgba(0,0,0,0.3); z-index: 2001; opacity: 0; visibility: hidden; transition: all 0.45s cubic-bezier(0.32, 0.72, 0, 1); color: #000000; overflow: hidden; will-change: transform, opacity; } .macos-window.show { opacity: 1; visibility: visible; transform: translate(-50%, -50%) scale(1); } .macos-header { background: #f1f1f1; padding: 12px 15px; display: flex; gap: 8px; align-items: center; justify-content: flex-start; } .mac-dot { width: 13px; height: 13px; border-radius: 50%; cursor: pointer; box-shadow: inset 0 0 4px rgba(0,0,0,0.1); } -.close-dot { background: #ff5f56; } .min-dot { background: #ffbd2e; } .max-dot { background: #27c93f; } -.macos-body { padding: 20px 15px 30px 15px; text-align: center; } -.info-desc { font-size: 15px; margin-top: 15px; line-height: 1.6; } +.close-dot { background: #ff5f56; } +.min-dot { background: #ffbd2e; } +.max-dot { background: #27c93f; } +.macos-body { padding: 25px 20px 30px 20px; text-align: center; } + +/* ============================================ + ⭐ POPUP INFO MỚI - 3 PILL BUTTONS KÍNH MỜ + ============================================ */ +.info-body { + padding: 18px 16px 24px !important; + text-align: center; +} +.info-pill-tabs { + display: flex; + gap: 6px; + justify-content: center; + margin-bottom: 20px; + flex-wrap: nowrap; +} +.info-pill { + padding: 9px 15px; + border-radius: 22px; + border: 1px solid rgba(0, 122, 255, 0.35); + background: rgba(0, 122, 255, 0.08); + color: #007aff; + font-size: 12.5px; + font-weight: 700; + text-decoration: none; + cursor: pointer; + transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + font-family: -apple-system, sans-serif; + white-space: nowrap; + display: flex; + align-items: center; + letter-spacing: 0.2px; + -webkit-tap-highlight-color: transparent; +} +.info-pill:active, .info-pill.active { + background: linear-gradient(135deg, #007aff, #0055cc); + color: white; + border-color: transparent; + box-shadow: 0 5px 16px rgba(0, 122, 255, 0.5), inset 0 1px 0 rgba(255,255,255,0.3); + transform: translateY(-1.5px) scale(1.02); +} + +.info-content { text-align: center; } +.info-app-name { + font-size: 22px; + font-weight: 800; + color: #007aff; + margin: 0 0 10px; + letter-spacing: 0.3px; +} +.info-version-row, .info-dev-row { + font-size: 13px; + color: #666; + margin-bottom: 8px; + display: flex; + justify-content: center; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} +.info-version-value { + font-weight: 700; + color: #000; + background: rgba(0,0,0,0.07); + padding: 3px 10px; + border-radius: 8px; + font-size: 12.5px; +} +.info-dev-link { + color: #007aff; + font-weight: 700; + text-decoration: none; +} +.info-dev-link:active { opacity: 0.7; } +.info-desc-box, .info-guide-box { + text-align: left; + margin-top: 14px; + padding: 12px 14px; + background: rgba(0,0,0,0.04); + border-radius: 12px; + border: 1px solid rgba(0,0,0,0.06); +} +.info-desc-title, .info-guide-title { + font-size: 13px; + font-weight: 700; + color: #007aff; + margin-bottom: 6px; + letter-spacing: 0.2px; +} +.info-desc-box p, .info-guide-box p { + font-size: 12.: 1.55; + color: #333; + margin: 0; + white-space: pre-line; +} -.settings-drawer.anim-zoom { right: 15px; transform: scale(0.6); opacity: 0; transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.3s ease; pointer-events: none; } +/* ============================================ + ANIMATION POPUP SETTINGS + ============================================ */ +.settings-drawer.anim-zoom { right: 15px; transform: scale(0.88); opacity: 0; transition: transform 0.45s cubic-bezier(0.32, 0.72, 0, 1), opacity 0.35s ease-out; pointer-events: none; } .settings-drawer.anim-zoom.open { transform: scale(1); opacity: 1; pointer-events: auto; } -.settings-drawer.anim-slide-top { right: 15px; transform: translateY(-120%); opacity: 0; transition: transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1), opacity 0.4s ease; pointer-events: none; } +.settings-drawer.anim-slide-top { right: 15px; transform: translateY(-120%); opacity: 0; transition: transform 0.45s cubic-bezier(0.32, 0.72, 0, 1), opacity 0.35s ease-out; pointer-events: none; } .settings-drawer.anim-slide-top.open { transform: translateY(0); opacity: 1; pointer-events: auto; } -.settings-drawer.anim-fade { right: 15px; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; } +.settings-drawer.anim-fade { right: 15px; opacity: 0; transition: opacity 0.45s cubic-bezier(0.32, 0.72, 0, 1); pointer-events: none; } .settings-drawer.anim-fade.open { opacity: 1; pointer-events: auto; } \ No newline at end of file From aec9daa8c7ca45b421d7c5a07317da258d4a1f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:39:12 +0700 Subject: [PATCH 05/23] Update vi-VN.json --- Language/vi-VN.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Language/vi-VN.json b/Language/vi-VN.json index 7f1fd7a..9dd5342 100644 --- a/Language/vi-VN.json +++ b/Language/vi-VN.json @@ -1,12 +1,18 @@ { "app_title": "Quick Control", "settings_title": "CÀI ĐẶT", + "tab_appearance": "🎨 Giao diện", + "tab_title": "✏️ Tiêu đề", + "tab_frame": "🖼️ Khung", + "tab_media": "🎵 Media", + "tab_shadow": "🌑 Bóng", "btn_list": "Danh sách", "btn_grid": "Lưới", "btn_export": "Xuất", "btn_import": "Nhập", "btn_edit_layout": "Sắp xếp", "btn_reset_default": "Khôi phục mặc định", + "btn_clear_bg": "Xóa ảnh nền", "lbl_theme_presets": "🪄 Giao diện", "opt_preset_none": "Tự do tùy chỉnh", "opt_preset_vn": "🇻🇳 VietOS", @@ -15,6 +21,17 @@ "opt_preset_White_button_(iPad)": "Nút trắng (iPad)", "opt_preset_neon_dark": "🌑 Neon Tối", "opt_preset_gold": "👑 Gold", + "lbl_quick_palette": "🎨 Bảng màu nhanh", + "swatch_bg_main": "Màu nền chính", + "swatch_bg_main_short": "Nền", + "swatch_text_color": "Màu chữ hệ thống", + "swatch_text_color_short": "Chữ", + "swatch_list_bg": "Nền danh sách", + "swatch_list_bg_short": "Nền DS", + "swatch_list_text": "Chữ danh sách", + "swatch_list_text_short": "Chữ DS", + "swatch_list_svg": "SVG danh sách", + "swatch_list_svg_short": "SVG DS", "lbl_title_adjust": "Sửa tiêu đề", "lbl_title_size": "🔠 Cỡ chữ chính", "lbl_title_spacing": "📐 Dãn chữ chính", @@ -46,6 +63,8 @@ "lbl_list_svg": "🏳️‍🌈 Màu SVG Danh sách", "lbl_theme_frame": "💠 CHỦ ĐỀ HỆ THỐNG", "lbl_frame_svg_size": "🖼️ Khung Icon & SVG", + "lbl_frame_size": "Khung", + "lbl_svg_size": "SVG", "lbl_svg_opacity": "Độ đậm SVG", "lbl_frame_radius": "Bo góc", "lbl_frame_color": "Nền khung", From 5b193ad7856db64a7e98a5b426891e5ee7491331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:41:26 +0700 Subject: [PATCH 06/23] Update theme.js --- js/theme.js | 1344 +++++++++++++++++++++++++++++---------------------- 1 file changed, 759 insertions(+), 585 deletions(-) diff --git a/js/theme.js b/js/theme.js index b27fa24..9420491 100644 --- a/js/theme.js +++ b/js/theme.js @@ -9,640 +9,814 @@ const SHADOW_MODES = [ ]; document.addEventListener("DOMContentLoaded", () => { - const root = document.documentElement; - const shadowContainer = document.getElementById('shadow-controls'); - const mainContainer = document.getElementById('main-container'); - const introScreen = document.getElementById('intro-screen'); - const introTextWrapper = document.getElementById('intro-text-wrapper'); - - if (introTextWrapper) { - const effects = ['anim-wave', 'anim-bounce', 'anim-flip']; - const randomEffect = effects[Math.floor(Math.random() * effects.length)]; - introTextWrapper.classList.add(randomEffect); - } + try { + const root = document.documentElement; + const shadowContainer = document.getElementById('shadow-controls'); + const mainContainer = document.getElementById('main-container'); + const introScreen = document.getElementById('intro-screen'); + const introTextWrapper = document.getElementById('intro-text-wrapper'); + + if (introTextWrapper) { + const effects = ['anim-wave', 'anim-bounce', 'anim-flip']; + const randomEffect = effects[Math.floor(Math.random() * effects.length)]; + introTextWrapper.classList.add(randomEffect); + } - mainContainer.classList.add('intro-zoom'); - - window.addEventListener('load', () => { - setTimeout(() => { - introScreen.classList.add('dismiss'); - mainContainer.classList.remove('intro-zoom'); - setTimeout(() => { introScreen.style.display = 'none'; }, 500); - }, 2000); - }); - - if (!DEV_MODE) document.body.classList.add('standard-mode'); - - const badgePreset = document.getElementById('badge-preset'); - const badgeTheme = document.getElementById('badge-theme'); - - if (localStorage.getItem('sttv_seen_preset_badge') === 'true') badgePreset?.classList.add('hidden'); - if (localStorage.getItem('sttv_seen_theme_badge') === 'true') badgeTheme?.classList.add('hidden'); - - let globalTooltip = document.createElement('div'); - globalTooltip.className = 'slider-tooltip'; - document.body.appendChild(globalTooltip); - - let rafId = null; - document.addEventListener('input', (e) => { - if (e.target && e.target.type === 'range') { - const input = e.target; - if (rafId) cancelAnimationFrame(rafId); - rafId = requestAnimationFrame(() => { - globalTooltip.innerText = input.value; - globalTooltip.classList.add('show'); - - const rect = input.getBoundingClientRect(); - const min = parseFloat(input.min) || 0; - const max = parseFloat(input.max) || 100; - const val = parseFloat(input.value); - const percent = (val - min) / (max - min); - - const thumbOffset = 12.5 - (percent * 25); - const thumbX = rect.left + (percent * rect.width) + thumbOffset; - - globalTooltip.style.left = `${thumbX}px`; - globalTooltip.style.top = `${rect.top - 35}px`; + if (mainContainer) mainContainer.classList.add('intro-zoom'); + + window.addEventListener('load', () => { + setTimeout(() => { + if (window.__dismissIntro) { window.__dismissIntro(); return; } + if (introScreen) { + introScreen.classList.add('dismiss'); + if (mainContainer) mainContainer.classList.remove('intro-zoom'); + setTimeout(() => { introScreen.style.display = 'none'; }, 500); + } + }, 2000); + }); + + if (!DEV_MODE) document.body.classList.add('standard-mode'); + + const badgePreset = document.getElementById('badge-preset'); + const badgeTheme = document.getElementById('badge-theme'); + + if (localStorage.getItem('sttv_seen_preset_badge') === 'true' && badgePreset) badgePreset.classList.add('hidden'); + if (localStorage.getItem('sttv_seen_theme_badge') === 'true' && badgeTheme) badgeTheme.classList.add('hidden'); + + /* ========== TAB SWITCHING ========== */ + const tabButtons = document.querySelectorAll('.settings-tab'); + const tabContents = document.querySelectorAll('.settings-tab-content'); + + tabButtons.forEach(btn => { + btn.addEventListener('click', () => { + const targetTab = btn.dataset.tab; + tabButtons.forEach(b => b.classList.remove('active')); + tabContents.forEach(c => c.classList.remove('active')); + btn.classList.add('active'); + const targetContent = document.querySelector(`.settings-tab-content[data-tab="${targetTab}"]`); + if (targetContent) targetContent.classList.add('active'); + try { localStorage.setItem('sttv_activeTab', targetTab); } catch(e) {} + try { playTick(); } catch(e) {} }); + }); - playTick(); - if (e.target.id !== 'val-theme-preset' && e.target.id !== 'val-popup-anim') { - resetPresetToCustom(); + const savedTab = localStorage.getItem('sttv_activeTab'); + if (savedTab) { + const savedBtn = document.querySelector(`.settings-tab[data-tab="${savedTab}"]`); + const savedContent = document.querySelector(`.settings-tab-content[data-tab="${savedTab}"]`); + if (savedBtn && savedContent) { + tabButtons.forEach(b => b.classList.remove('active')); + tabContents.forEach(c => c.classList.remove('active')); + savedBtn.classList.add('active'); + savedContent.classList.add('active'); } - updateLiveVariables(false); } - }); - const hideTooltip = (e) => { - if (e.target && e.target.type === 'range') { - globalTooltip.classList.remove('show'); + /* ========== TOOLTIP GLOBAL ========== */ + let globalTooltip = document.createElement('div'); + globalTooltip.className = 'slider-tooltip'; + document.body.appendChild(globalTooltip); + + let rafId = null; + document.addEventListener('input', (e) => { + if (e.target && e.target.type === 'range') { + const input = e.target; + if (rafId) cancelAnimationFrame(rafId); + rafId = requestAnimationFrame(() => { + globalTooltip.innerText = input.value; + globalTooltip.classList.add('show'); + const rect = input.getBoundingClientRect(); + const min = parseFloat(input.min) || 0; + const max = parseFloat(input.max) || 100; + const val = parseFloat(input.value); + const percent = (val - min) / (max - min); + const thumbOffset = 12.5 - (percent * 25); + const thumbX = rect.left + (percent * rect.width) + thumbOffset; + globalTooltip.style.left = `${thumbX}px`; + globalTooltip.style.top = `${rect.top - 35}px`; + }); + try { playTick(); } catch(e) {} + if (e.target.id !== 'val-theme-preset' && e.target.id !== 'val-popup-anim') { + resetPresetToCustom(); + } + updateLiveVariables(false); + } + }); + + const hideTooltip = (e) => { + if (e.target && e.target.type === 'range') globalTooltip.classList.remove('show'); + }; + document.addEventListener('pointerup', hideTooltip); + document.addEventListener('touchend', hideTooltip); + document.addEventListener('change', (e) => { + if (e.target && e.target.type === 'range') { + hideTooltip(e); + saveSettingsToLocal(); + } + }); + + /* ========== SHADOW CONTROLS ========== */ + if (shadowContainer) { + SHADOW_MODES.forEach(mode => { + const div = document.createElement('div'); + div.className = 'shadow-item dev-only'; + div.innerHTML = ` +
+ ${mode.name} + +
+
+
+
+
+
+
+
+
+ `; + shadowContainer.appendChild(div); + }); + } + + let currentThemeTarget = 'frame'; + + function syncThemePickerVisuals(val) { + document.querySelectorAll('.theme-chip').forEach(c => { + if (c.dataset.value === val) c.classList.add('active'); + else c.classList.remove('active'); + }); } - }; - - document.addEventListener('pointerup', hideTooltip); - document.addEventListener('touchend', hideTooltip); - document.addEventListener('change', (e) => { - if (e.target && e.target.type === 'range') { - hideTooltip(e); - saveSettingsToLocal(); + + const themeModal = document.getElementById('theme-modal'); + const themeOverlay = document.getElementById('theme-overlay'); + + const btnMoreThemes = document.getElementById('btn-more-themes'); + if (btnMoreThemes) { + btnMoreThemes.onclick = () => { + currentThemeTarget = 'frame'; + const vtf = document.getElementById('val-theme-frame'); + if (vtf) syncThemePickerVisuals(vtf.value); + if (themeModal) themeModal.classList.add('show'); + if (themeOverlay) themeOverlay.classList.add('show'); + }; + } + + const btnThumbTheme = document.getElementById('btn-thumb-theme'); + if (btnThumbTheme) { + btnThumbTheme.onclick = () => { + currentThemeTarget = 'thumb'; + const vtt = document.getElementById('val-thumb-theme'); + if (vtt) syncThemePickerVisuals(vtt.value); + if (themeModal) themeModal.classList.add('show'); + if (themeOverlay) themeOverlay.classList.add('show'); + }; } - }); - - SHADOW_MODES.forEach(mode => { - const div = document.createElement('div'); - div.className = 'shadow-item dev-only'; - div.innerHTML = ` -
- ${mode.name} - -
-
-
-
-
-
-
-
-
- `; - shadowContainer.appendChild(div); - }); - - let currentThemeTarget = 'frame'; - - function syncThemePickerVisuals(val) { - document.querySelectorAll('.theme-chip').forEach(c => { - if (c.dataset.value === val) c.classList.add('active'); - else c.classList.remove('active'); + + document.querySelectorAll('.theme-chip').forEach(chip => { + chip.addEventListener('click', () => { + const val = chip.dataset.value; + syncThemePickerVisuals(val); + if (currentThemeTarget === 'frame') { + const vtf = document.getElementById('val-theme-frame'); + if (vtf) vtf.value = val; + if (badgeTheme) { badgeTheme.classList.add('hidden'); localStorage.setItem('sttv_seen_theme_badge', 'true'); } + resetPresetToCustom(); + } else if (currentThemeTarget === 'thumb') { + const vtt = document.getElementById('val-thumb-theme'); + if (vtt) vtt.value = val; + } + updateLiveVariables(true); + }); }); - } - const themeModal = document.getElementById('theme-modal'); - const themeOverlay = document.getElementById('theme-overlay'); - - const btnMoreThemes = document.getElementById('btn-more-themes'); - if (btnMoreThemes) { - btnMoreThemes.onclick = () => { - currentThemeTarget = 'frame'; - syncThemePickerVisuals(document.getElementById('val-theme-frame').value); - themeModal.classList.add('show'); themeOverlay.classList.add('show'); + const closeThemeModal = document.getElementById('close-theme-modal'); + if (closeThemeModal && themeModal) closeThemeModal.onclick = () => { + themeModal.classList.remove('show'); + if (themeOverlay) themeOverlay.classList.remove('show'); }; - } - - const btnThumbTheme = document.getElementById('btn-thumb-theme'); - if (btnThumbTheme) { - btnThumbTheme.onclick = () => { - currentThemeTarget = 'thumb'; - syncThemePickerVisuals(document.getElementById('val-thumb-theme').value); - themeModal.classList.add('show'); themeOverlay.classList.add('show'); + + const infoModal = document.getElementById('info-modal'); + const btnInfo = document.getElementById('btn-info'); + if (btnInfo && infoModal) btnInfo.onclick = () => { + infoModal.classList.add('show'); + if (themeOverlay) themeOverlay.classList.add('show'); + }; + const closeInfoModal = document.getElementById('close-info-modal'); + if (closeInfoModal && infoModal) closeInfoModal.onclick = () => { + infoModal.classList.remove('show'); + if (themeOverlay) themeOverlay.classList.remove('show'); }; - } - document.querySelectorAll('.theme-chip').forEach(chip => { - chip.addEventListener('click', () => { - const val = chip.dataset.value; - syncThemePickerVisuals(val); - - if (currentThemeTarget === 'frame') { - document.getElementById('val-theme-frame').value = val; - if (badgeTheme) { badgeTheme.classList.add('hidden'); localStorage.setItem('sttv_seen_theme_badge', 'true'); } - resetPresetToCustom(); - } else if (currentThemeTarget === 'thumb') { - document.getElementById('val-thumb-theme').value = val; + if (themeOverlay) { + themeOverlay.onclick = () => { + if (themeModal) themeModal.classList.remove('show'); + if (infoModal) infoModal.classList.remove('show'); + themeOverlay.classList.remove('show'); + }; + } + + const presetSelect = document.getElementById('val-theme-preset'); + if (presetSelect) { + presetSelect.addEventListener('change', (e) => { + const val = e.target.value; + localStorage.setItem('sttv_activePreset', val); + if (badgePreset) { badgePreset.classList.add('hidden'); localStorage.setItem('sttv_seen_preset_badge', 'true'); } + if (val !== 'none') unpackConfig(val); + }); + } + + function resetPresetToCustom() { + if (presetSelect) { + presetSelect.value = 'none'; + localStorage.setItem('sttv_activePreset', 'none'); } - updateLiveVariables(true); + } + + const drawerEl = document.getElementById('settings-drawer'); + const popupAnimSelect = document.getElementById('val-popup-anim'); + if (popupAnimSelect && drawerEl) { + popupAnimSelect.addEventListener('change', (e) => { + const animClass = e.target.value; + drawerEl.className = 'settings-drawer ' + (drawerEl.classList.contains('open') ? 'open ' : '') + (animClass !== 'default' ? animClass : ''); + localStorage.setItem('sttv_popupAnim', animClass); + }); + } + + const AudioContext = window.AudioContext || window.webkitAudioContext; + let audioCtx = null; + function playTick() { + const toggleAudio = document.getElementById('toggle-audio'); + if (!toggleAudio || !toggleAudio.checked) return; + try { + if (!audioCtx) audioCtx = new AudioContext(); + if (audioCtx.state === 'suspended') audioCtx.resume(); + const osc = audioCtx.createOscillator(); + const gain = audioCtx.createGain(); + osc.type = 'sine'; + osc.frequency.setValueAtTime(800, audioCtx.currentTime); + osc.frequency.exponentialRampToValueAtTime(300, audioCtx.currentTime + 0.04); + gain.gain.setValueAtTime(0.2, audioCtx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.04); + osc.connect(gain); gain.connect(audioCtx.destination); + osc.start(); osc.stop(audioCtx.currentTime + 0.04); + } catch(e) {} + } + document.addEventListener('click', (e) => { + if (e.target.tagName === 'BUTTON' || e.target.type === 'checkbox' || e.target.closest('.theme-chip') || e.target.closest('.theme-chip-more')) playTick(); }); - }); - - const closeThemeModal = document.getElementById('close-theme-modal'); - if (closeThemeModal) closeThemeModal.onclick = () => { themeModal.classList.remove('show'); themeOverlay.classList.remove('show'); }; - - const infoModal = document.getElementById('info-modal'); - const btnInfo = document.getElementById('btn-info'); - if (btnInfo && infoModal) btnInfo.onclick = () => { infoModal.classList.add('show'); themeOverlay.classList.add('show'); }; - const closeInfoModal = document.getElementById('close-info-modal'); - if (closeInfoModal && infoModal) closeInfoModal.onclick = () => { infoModal.classList.remove('show'); themeOverlay.classList.remove('show'); }; - - if (themeOverlay) { - themeOverlay.onclick = () => { - if(themeModal) themeModal.classList.remove('show'); - if(infoModal) infoModal.classList.remove('show'); - themeOverlay.classList.remove('show'); - }; - } - const presetSelect = document.getElementById('val-theme-preset'); - presetSelect.addEventListener('change', (e) => { - const val = e.target.value; - localStorage.setItem('sttv_activePreset', val); - if (badgePreset) { badgePreset.classList.add('hidden'); localStorage.setItem('sttv_seen_preset_badge', 'true'); } - if (val !== 'none') unpackConfig(val); - }); - - function resetPresetToCustom() { - presetSelect.value = 'none'; - localStorage.setItem('sttv_activePreset', 'none'); - } + if (drawerEl) { + drawerEl.addEventListener('input', (e) => { if (e.target.type === 'color') updateLiveVariables(true); }); + drawerEl.addEventListener('change', (e) => { + if (e.target.type === 'color') { e.target.blur(); updateLiveVariables(true); } + else if (e.target.type === 'checkbox') updateLiveVariables(true); + }); + } - const drawerEl = document.getElementById('settings-drawer'); - const popupAnimSelect = document.getElementById('val-popup-anim'); - popupAnimSelect.addEventListener('change', (e) => { - const animClass = e.target.value; - drawerEl.className = 'settings-drawer ' + (drawerEl.classList.contains('open') ? 'open ' : '') + (animClass !== 'default' ? animClass : ''); - localStorage.setItem('sttv_popupAnim', animClass); - }); - - const AudioContext = window.AudioContext || window.webkitAudioContext; - let audioCtx = null; - function playTick() { - if (!document.getElementById('toggle-audio').checked) return; - if (!audioCtx) audioCtx = new AudioContext(); - if (audioCtx.state === 'suspended') audioCtx.resume(); - const osc = audioCtx.createOscillator(); - const gain = audioCtx.createGain(); - osc.type = 'sine'; osc.frequency.setValueAtTime(800, audioCtx.currentTime); osc.frequency.exponentialRampToValueAtTime(300, audioCtx.currentTime + 0.04); - gain.gain.setValueAtTime(0.2, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.04); - osc.connect(gain); gain.connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + 0.04); - } - document.addEventListener('click', (e) => { if (e.target.tagName === 'BUTTON' || e.target.type === 'checkbox' || e.target.closest('.theme-chip') || e.target.closest('.theme-chip-more')) playTick(); }); - - drawerEl.addEventListener('input', (e) => { if (e.target.type === 'color') updateLiveVariables(true); }); - drawerEl.addEventListener('change', (e) => { - if (e.target.type === 'color') { e.target.blur(); updateLiveVariables(true); } - else if (e.target.type === 'checkbox') updateLiveVariables(true); - }); - - document.querySelectorAll('.shadow-switch').forEach(switchBtn => { - switchBtn.addEventListener('change', (e) => { - const drawer = document.getElementById(`drawer-${e.target.value}`); - if (e.target.checked) drawer.classList.add('active'); else drawer.classList.remove('active'); - resetPresetToCustom(); updateLiveVariables(true); + document.querySelectorAll('.shadow-switch').forEach(switchBtn => { + switchBtn.addEventListener('change', (e) => { + const drawer = document.getElementById(`drawer-${e.target.value}`); + if (drawer) { + if (e.target.checked) drawer.classList.add('active'); + else drawer.classList.remove('active'); + } + resetPresetToCustom(); + updateLiveVariables(true); + }); }); - }); - - function handleOrientation(e) { - if (!document.getElementById('toggle-parallax').checked) return; - let x = e.gamma; let y = e.beta; - if (x > 45) x = 45; if (x < -45) x = -45; - if (y > 45) y = 45; if (y < -45) y = -45; - root.style.setProperty('--tilt-x', (x / 10) + 'px'); root.style.setProperty('--tilt-y', (y / 10) + 'px'); - } - document.getElementById('toggle-parallax').addEventListener('change', (e) => { - if (e.target.checked) { - if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') { - DeviceOrientationEvent.requestPermission().then(state => { - if (state === 'granted') window.addEventListener('deviceorientation', handleOrientation); - else { e.target.checked = false; alert('Vui lòng cấp quyền cảm biến!'); } - }).catch(console.error); - } else { window.addEventListener('deviceorientation', handleOrientation); } - } else { - window.removeEventListener('deviceorientation', handleOrientation); - root.style.setProperty('--tilt-x', '0px'); root.style.setProperty('--tilt-y', '0px'); + function handleOrientation(e) { + const tp = document.getElementById('toggle-parallax'); + if (!tp || !tp.checked) return; + let x = e.gamma; let y = e.beta; + if (x > 45) x = 45; if (x < -45) x = -45; + if (y > 45) y = 45; if (y < -45) y = -45; + root.style.setProperty('--tilt-x', (x / 10) + 'px'); + root.style.setProperty('--tilt-y', (y / 10) + 'px'); } - localStorage.setItem('sttv_parallax', e.target.checked); - }); - - const uploadBg = document.getElementById('upload-bg'); - uploadBg.addEventListener('change', (e) => { - const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (ev) => { - const b64 = ev.target.result; localStorage.setItem('sttv_customBgImage', b64); - root.style.setProperty('--bg-image', `url('${b64}')`); - }; - reader.readAsDataURL(file); + + const toggleParallaxEl = document.getElementById('toggle-parallax'); + if (toggleParallaxEl) { + toggleParallaxEl.addEventListener('change', (e) => { + if (e.target.checked) { + if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') { + DeviceOrientationEvent.requestPermission().then(state => { + if (state === 'granted') window.addEventListener('deviceorientation', handleOrientation); + else { e.target.checked = false; alert('Vui lòng cấp quyền cảm biến!'); } + }).catch(console.error); + } else { window.addEventListener('deviceorientation', handleOrientation); } + } else { + window.removeEventListener('deviceorientation', handleOrientation); + root.style.setProperty('--tilt-x', '0px'); + root.style.setProperty('--tilt-y', '0px'); + } + localStorage.setItem('sttv_parallax', e.target.checked); + }); } - }); - document.getElementById('clear-bg').addEventListener('click', () => { - localStorage.removeItem('sttv_customBgImage'); root.style.setProperty('--bg-image', 'none'); uploadBg.value = ""; - }); - - const overlay = document.getElementById('settings-overlay'); - const closeSettings = () => { drawerEl.classList.remove('open'); overlay.classList.remove('open'); }; - document.getElementById('open-settings').onclick = () => { drawerEl.classList.add('open'); overlay.classList.add('open'); }; - document.getElementById('close-settings').onclick = closeSettings; - overlay.onclick = closeSettings; - - let adjustTimeout; - drawerEl.addEventListener('input', (e) => { - if (e.target.tagName === 'INPUT' && e.target.type !== 'color') { - drawerEl.classList.add('adjusting'); clearTimeout(adjustTimeout); - adjustTimeout = setTimeout(() => drawerEl.classList.remove('adjusting'), 800); + + const uploadBg = document.getElementById('upload-bg'); + if (uploadBg) { + uploadBg.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (ev) => { + const b64 = ev.target.result; + localStorage.setItem('sttv_customBgImage', b64); + root.style.setProperty('--bg-image', `url('${b64}')`); + }; + reader.readAsDataURL(file); + } + }); + } + const clearBgBtn = document.getElementById('clear-bg'); + if (clearBgBtn) { + clearBgBtn.addEventListener('click', () => { + localStorage.removeItem('sttv_customBgImage'); + root.style.setProperty('--bg-image', 'none'); + if (uploadBg) uploadBg.value = ""; + }); } - }); - function setLayoutMode(mode) { localStorage.setItem('sttv_layoutMode', mode); updateLiveVariables(true); } - document.getElementById('btn-layout-list').onclick = () => { setLayoutMode('list'); resetPresetToCustom(); }; - document.getElementById('btn-layout-grid').onclick = () => { setLayoutMode('grid'); resetPresetToCustom(); }; + const overlay = document.getElementById('settings-overlay'); + const closeSettings = () => { + if (drawerEl) drawerEl.classList.remove('open'); + if (overlay) overlay.classList.remove('open'); + }; + const openSettingsBtn = document.getElementById('open-settings'); + const closeSettingsBtn = document.getElementById('close-settings'); + if (openSettingsBtn) openSettingsBtn.onclick = () => { + if (drawerEl) drawerEl.classList.add('open'); + if (overlay) overlay.classList.add('open'); + }; + if (closeSettingsBtn) closeSettingsBtn.onclick = closeSettings; + if (overlay) overlay.onclick = closeSettings; + + let adjustTimeout; + if (drawerEl) { + drawerEl.addEventListener('input', (e) => { + if (e.target.tagName === 'INPUT' && e.target.type !== 'color') { + drawerEl.classList.add('adjusting'); + clearTimeout(adjustTimeout); + adjustTimeout = setTimeout(() => drawerEl.classList.remove('adjusting'), 800); + } + }); + } - function hexToRgba(hex, alpha) { - let r = 0, g = 0, b = 0; - if (hex.length === 4) { r = parseInt(hex[1] + hex[1], 16); g = parseInt(hex[2] + hex[2], 16); b = parseInt(hex[3] + hex[3], 16); } - else if (hex.length === 7) { r = parseInt(hex.substring(1, 3), 16); g = parseInt(hex.substring(3, 5), 16); b = parseInt(hex.substring(5, 7), 16); } - return `rgba(${r}, ${g}, ${b}, ${alpha / 100})`; - } - function hexToRgb(hex) { - let r = 0, g = 0, b = 0; - if (hex.length === 4) { r = parseInt(hex[1] + hex[1], 16); g = parseInt(hex[2] + hex[2], 16); b = parseInt(hex[3] + hex[3], 16); } - else if (hex.length === 7) { r = parseInt(hex.substring(1, 3), 16); g = parseInt(hex.substring(3, 5), 16); b = parseInt(hex.substring(5, 7), 16); } - return `${r}, ${g}, ${b}`; - } + function setLayoutMode(mode) { localStorage.setItem('sttv_layoutMode', mode); updateLiveVariables(true); } + const btnLayoutList = document.getElementById('btn-layout-list'); + const btnLayoutGrid = document.getElementById('btn-layout-grid'); + if (btnLayoutList) btnLayoutList.onclick = () => { setLayoutMode('list'); resetPresetToCustom(); }; + if (btnLayoutGrid) btnLayoutGrid.onclick = () => { setLayoutMode('grid'); resetPresetToCustom(); }; + + function hexToRgba(hex, alpha) { + let r = 0, g = 0, b = 0; + if (hex.length === 4) { r = parseInt(hex[1] + hex[1], 16); g = parseInt(hex[2] + hex[2], 16); b = parseInt(hex[3] + hex[3], 16); } + else if (hex.length === 7) { r = parseInt(hex.substring(1, 3), 16); g = parseInt(hex.substring(3, 5), 16); b = parseInt(hex.substring(5, 7), 16); } + return `rgba(${r}, ${g}, ${b}, ${alpha / 100})`; + } + function hexToRgb(hex) { + let r = 0, g = 0, b = 0; + if (hex.length === 4) { r = parseInt(hex[1] + hex[1], 16); g = parseInt(hex[2] + hex[2], 16); b = parseInt(hex[3] + hex[3], 16); } + else if (hex.length === 7) { r = parseInt(hex.substring(1, 3), 16); g = parseInt(hex.substring(3, 5), 16); b = parseInt(hex.substring(5, 7), 16); } + return `${r}, ${g}, ${b}`; + } - function updateShadow() { - const activeShadows = document.querySelectorAll('input[name="active_shadow"]:checked'); - let combinedShadow = ''; - activeShadows.forEach(checkbox => { - const drawer = document.getElementById(`drawer-${checkbox.value}`); - if (!drawer) return; - const mode = SHADOW_MODES.find(m => m.id === checkbox.value); - const rgbaColor = hexToRgba(drawer.querySelector('.s-c').value, drawer.querySelector('.s-o').value); - let shadowStr = mode.template - .replace('{x}', drawer.querySelector('.s-x').value).replace('{y}', drawer.querySelector('.s-y').value) - .replace('{b}', drawer.querySelector('.s-b').value).replace('{s}', drawer.querySelector('.s-s').value) - .replace('{c}', rgbaColor); - if (combinedShadow) combinedShadow += ', '; combinedShadow += shadowStr; - }); - root.style.setProperty('--btn-shadow', combinedShadow || 'none'); - } + function updateShadow() { + const activeShadows = document.querySelectorAll('input[name="active_shadow"]:checked'); + let combinedShadow = ''; + activeShadows.forEach(checkbox => { + const drawer = document.getElementById(`drawer-${checkbox.value}`); + if (!drawer) return; + const mode = SHADOW_MODES.find(m => m.id === checkbox.value); + const rgbaColor = hexToRgba(drawer.querySelector('.s-c').value, drawer.querySelector('.s-o').value); + let shadowStr = mode.template + .replace('{x}', drawer.querySelector('.s-x').value).replace('{y}', drawer.querySelector('.s-y').value) + .replace('{b}', drawer.querySelector('.s-b').value).replace('{s}', drawer.querySelector('.s-s').value) + .replace('{c}', rgbaColor); + if (combinedShadow) combinedShadow += ', '; + combinedShadow += shadowStr; + }); + root.style.setProperty('--btn-shadow', combinedShadow || 'none'); + } - function saveSettingsToLocal() { - localStorage.setItem('sttv_bgMain', document.getElementById('val-bg-main').value); - localStorage.setItem('sttv_textColor', document.getElementById('val-text-color').value); - localStorage.setItem('sttv_listBg', document.getElementById('val-list-bg').value); - localStorage.setItem('sttv_listBgOpacity', document.getElementById('val-list-bg-opacity').value); - localStorage.setItem('sttv_listText', document.getElementById('val-list-text').value); - localStorage.setItem('sttv_listSvg', document.getElementById('val-list-svg').value); - localStorage.setItem('sttv_themeFrame', document.getElementById('val-theme-frame').value); - localStorage.setItem('sttv_frameSize', document.getElementById('val-frame-size').value); - localStorage.setItem('sttv_svgSize', document.getElementById('val-svg-size').value); - localStorage.setItem('sttv_svgOpacity', document.getElementById('val-svg-opacity').value); - localStorage.setItem('sttv_frameRadius', document.getElementById('val-frame-radius').value); - localStorage.setItem('sttv_frameColor', document.getElementById('val-frame-color').value); - localStorage.setItem('sttv_frameBgOpacity', document.getElementById('val-frame-bg-opacity').value); - localStorage.setItem('sttv_svgColor', document.getElementById('val-svg-color').value); - - localStorage.setItem('sttv_hideLabels', document.getElementById('toggle-hide-labels').checked); - localStorage.setItem('sttv_listFrame', document.getElementById('toggle-list-frame').checked); - localStorage.setItem('sttv_titleSize', document.getElementById('val-title-size').value); - localStorage.setItem('sttv_titleSpacing', document.getElementById('val-title-spacing').value); - localStorage.setItem('sttv_iconSize', document.getElementById('val-icon-size').value); - localStorage.setItem('sttv_iconSpacing', document.getElementById('val-icon-spacing').value); - localStorage.setItem('sttv_glassMode', document.getElementById('toggle-glass').checked); - localStorage.setItem('sttv_audioFeedback', document.getElementById('toggle-audio').checked); - - localStorage.setItem('sttv_mediaWidth', document.getElementById('val-media-width').value); - localStorage.setItem('sttv_mediaBgOpacity', document.getElementById('val-media-bg-opacity').value); - localStorage.setItem('sttv_mediaBtnSize', document.getElementById('val-media-btn-size').value); - localStorage.setItem('sttv_mediaBgColor', document.getElementById('val-media-bg-color').value); - localStorage.setItem('sttv_mediaBtnColor', document.getElementById('val-media-btn-color').value); - localStorage.setItem('sttv_mediaSvgColor', document.getElementById('val-media-svg-color').value); - localStorage.setItem('sttv_mediaThemeSync', document.getElementById('toggle-media-theme').checked); - localStorage.setItem('sttv_themeDot', document.getElementById('toggle-theme-dot').checked); - - localStorage.setItem('sttv_thumbSize', document.getElementById('val-thumb-size').value); - localStorage.setItem('sttv_thumbColor', document.getElementById('val-thumb-color').value); - localStorage.setItem('sttv_thumbTheme', document.getElementById('val-thumb-theme').value); - - const shadowState = {}; - SHADOW_MODES.forEach(mode => { - const drawer = document.getElementById(`drawer-${mode.id}`); - const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode.id}"]`); - if (drawer && checkbox) { - shadowState[mode.id] = { - active: checkbox.checked, x: drawer.querySelector('.s-x').value, y: drawer.querySelector('.s-y').value, - b: drawer.querySelector('.s-b').value, s: drawer.querySelector('.s-s').value, - c: drawer.querySelector('.s-c').value, o: drawer.querySelector('.s-o').value - }; - } - }); - localStorage.setItem('sttv_shadowConfig', JSON.stringify(shadowState)); - } + function g(id) { const el = document.getElementById(id); return el ? el.value : ''; } + function c(id) { const el = document.getElementById(id); return el ? el.checked : false; } - function packConfig() { - const shadowArr = []; - SHADOW_MODES.forEach(mode => { - const drawer = document.getElementById(`drawer-${mode.id}`); - const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode.id}"]`); - if (drawer && checkbox && checkbox.checked) { - shadowArr.push([ - mode.id, drawer.querySelector('.s-x').value, drawer.querySelector('.s-y').value, - drawer.querySelector('.s-b').value, drawer.querySelector('.s-s').value, - drawer.querySelector('.s-c').value, drawer.querySelector('.s-o').value - ].join('*')); - } - }); + function saveSettingsToLocal() { + try { + localStorage.setItem('sttv_bgMain', g('val-bg-main')); + localStorage.setItem('sttv_textColor', g('val-text-color')); + localStorage.setItem('sttv_listBg', g('val-list-bg')); + localStorage.setItem('sttv_listBgOpacity', g('val-list-bg-opacity')); + localStorage.setItem('sttv_listText', g('val-list-text')); + localStorage.setItem('sttv_listSvg', g('val-list-svg')); + localStorage.setItem('sttv_themeFrame', g('val-theme-frame')); + localStorage.setItem('sttv_frameSize', g('val-frame-size')); + localStorage.setItem('sttv_svgSize', g('val-svg-size')); + localStorage.setItem('sttv_svgOpacity', g('val-svg-opacity')); + localStorage.setItem('sttv_frameRadius', g('val-frame-radius')); + localStorage.setItem('sttv_frameColor', g('val-frame-color')); + localStorage.setItem('sttv_frameBgOpacity', g('val-frame-bg-opacity')); + localStorage.setItem('sttv_svgColor', g('val-svg-color')); + localStorage.setItem('sttv_hideLabels', c('toggle-hide-labels')); + localStorage.setItem('sttv_listFrame', c('toggle-list-frame')); + localStorage.setItem('sttv_titleSize', g('val-title-size')); + localStorage.setItem('sttv_titleSpacing', g('val-title-spacing')); + localStorage.setItem('sttv_iconSize', g('val-icon-size')); + localStorage.setItem('sttv_iconSpacing', g('val-icon-spacing')); + localStorage.setItem('sttv_glassMode', c('toggle-glass')); + localStorage.setItem('sttv_audioFeedback', c('toggle-audio')); + localStorage.setItem('sttv_mediaWidth', g('val-media-width')); + localStorage.setItem('sttv_mediaBgOpacity', g('val-media-bg-opacity')); + localStorage.setItem('sttv_mediaBtnSize', g('val-media-btn-size')); + localStorage.setItem('sttv_mediaBgColor', g('val-media-bg-color')); + localStorage.setItem('sttv_mediaBtnColor', g('val-media-btn-color')); + localStorage.setItem('sttv_mediaSvgColor', g('val-media-svg-color')); + localStorage.setItem('sttv_mediaThemeSync', c('toggle-media-theme')); + localStorage.setItem('sttv_themeDot', c('toggle-theme-dot')); + localStorage.setItem('sttv_thumbSize', g('val-thumb-size')); + localStorage.setItem('sttv_thumbColor', g('val-thumb-color')); + localStorage.setItem('sttv_thumbTheme', g('val-thumb-theme')); + + const shadowState = {}; + SHADOW_MODES.forEach(mode => { + const drawer = document.getElementById(`drawer-${mode.id}`); + const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode.id}"]`); + if (drawer && checkbox) { + shadowState[mode.id] = { + active: checkbox.checked, x: drawer.querySelector('.s-x').value, y: drawer.querySelector('.s-y').value, + b: drawer.querySelector('.s-b').value, s: drawer.querySelector('.s-s').value, + c: drawer.querySelector('.s-c').value, o: drawer.querySelector('.s-o').value + }; + } + }); + localStorage.setItem('sttv_shadowConfig', JSON.stringify(shadowState)); + } catch(e) { console.warn('save failed:', e); } + } - const configValues = [ - document.getElementById('val-bg-main').value, document.getElementById('val-text-color').value, - localStorage.getItem('sttv_layoutMode') || 'grid', document.getElementById('val-list-bg').value, - document.getElementById('val-list-text').value, document.getElementById('val-list-svg').value, - document.getElementById('val-theme-frame').value, document.getElementById('val-frame-size').value, - document.getElementById('val-svg-size').value, document.getElementById('val-svg-opacity').value, - document.getElementById('val-frame-radius').value, document.getElementById('val-frame-color').value, - document.getElementById('val-svg-color').value, document.getElementById('toggle-hide-labels').checked ? 1 : 0, - document.getElementById('val-title-size').value, document.getElementById('val-title-spacing').value, - document.getElementById('toggle-list-frame').checked ? 1 : 0, document.getElementById('val-icon-size').value, - document.getElementById('val-icon-spacing').value, document.getElementById('val-list-bg-opacity').value, - document.getElementById('val-frame-bg-opacity').value, document.getElementById('val-media-width').value, - document.getElementById('val-media-bg-opacity').value, document.getElementById('val-media-btn-size').value, - document.getElementById('val-media-bg-color').value, document.getElementById('val-media-btn-color').value, - document.getElementById('val-media-svg-color').value, document.getElementById('toggle-glass').checked ? 1 : 0, - document.getElementById('val-popup-anim').value, shadowArr.join('~'), - document.getElementById('toggle-media-theme').checked ? 1 : 0, - document.getElementById('toggle-theme-dot').checked ? 1 : 0, - document.getElementById('val-thumb-size').value, - document.getElementById('val-thumb-color').value, - document.getElementById('val-thumb-theme').value - ]; - return encodeURIComponent(configValues.join('|')); - } + function packConfig() { + const shadowArr = []; + SHADOW_MODES.forEach(mode => { + const drawer = document.getElementById(`drawer-${mode.id}`); + const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode.id}"]`); + if (drawer && checkbox && checkbox.checked) { + shadowArr.push([ + mode.id, drawer.querySelector('.s-x').value, drawer.querySelector('.s-y').value, + drawer.querySelector('.s-b').value, drawer.querySelector('.s-s').value, + drawer.querySelector('.s-c').value, drawer.querySelector('.s-o').value + ].join('*')); + } + }); - function unpackConfig(code) { - if (!code) return; - try { - drawerEl.classList.remove('adjusting'); localStorage.removeItem('sttv_customBgImage'); - root.style.setProperty('--bg-image', 'none'); - if (document.getElementById('upload-bg')) document.getElementById('upload-bg').value = ""; + const configValues = [ + g('val-bg-main'), g('val-text-color'), + localStorage.getItem('sttv_layoutMode') || 'grid', g('val-list-bg'), + g('val-list-text'), g('val-list-svg'), + g('val-theme-frame'), g('val-frame-size'), + g('val-svg-size'), g('val-svg-opacity'), + g('val-frame-radius'), g('val-frame-color'), + g('val-svg-color'), c('toggle-hide-labels') ? 1 : 0, + g('val-title-size'), g('val-title-spacing'), + c('toggle-list-frame') ? 1 : 0, g('val-icon-size'), + g('val-icon-spacing'), g('val-list-bg-opacity'), + g('val-frame-bg-opacity'), g('val-media-width'), + g('val-media-bg-opacity'), g('val-media-btn-size'), + g('val-media-bg-color'), g('val-media-btn-color'), + g('val-media-svg-color'), c('toggle-glass') ? 1 : 0, + g('val-popup-anim'), shadowArr.join('~'), + c('toggle-media-theme') ? 1 : 0, + c('toggle-theme-dot') ? 1 : 0, + g('val-thumb-size'), + g('val-thumb-color'), + g('val-thumb-theme') + ]; + return encodeURIComponent(configValues.join('|')); + } - const decoded = decodeURIComponent(code.trim()); - const data = decoded.split('|'); - if (data.length < 29) { alert("Mã cấu hình không hợp lệ!"); return; } - - document.getElementById('val-bg-main').value = data[0]; document.getElementById('val-text-color').value = data[1]; - setLayoutMode(data[2]); document.getElementById('val-list-bg').value = data[3]; - document.getElementById('val-list-text').value = data[4]; document.getElementById('val-list-svg').value = data[5]; - document.getElementById('val-theme-frame').value = data[6]; - document.getElementById('val-frame-size').value = data[7]; document.getElementById('val-svg-size').value = data[8]; - document.getElementById('val-svg-opacity').value = data[9]; document.getElementById('val-frame-radius').value = data[10]; - document.getElementById('val-frame-color').value = data[11]; document.getElementById('val-svg-color').value = data[12]; - document.getElementById('toggle-hide-labels').checked = data[13] === '1'; document.getElementById('val-title-size').value = data[14]; - document.getElementById('val-title-spacing').value = data[15]; document.getElementById('toggle-list-frame').checked = data[16] === '1'; - document.getElementById('val-icon-size').value = data[17]; document.getElementById('val-icon-spacing').value = data[18]; - document.getElementById('val-list-bg-opacity').value = data[19]; document.getElementById('val-frame-bg-opacity').value = data[20]; - document.getElementById('val-media-width').value = data[21]; document.getElementById('val-media-bg-opacity').value = data[22]; - document.getElementById('val-media-btn-size').value = data[23]; document.getElementById('val-media-bg-color').value = data[24]; - document.getElementById('val-media-btn-color').value = data[25]; document.getElementById('val-media-svg-color').value = data[26]; - document.getElementById('toggle-glass').checked = data[27] === '1'; document.getElementById('val-popup-anim').value = data[28]; - - document.querySelectorAll('.shadow-switch').forEach(c => { - c.checked = false; const drw = document.getElementById(`drawer-${c.value}`); if (drw) drw.classList.remove('active'); - }); + function unpackConfig(code) { + if (!code) return; + try { + if (drawerEl) drawerEl.classList.remove('adjusting'); + localStorage.removeItem('sttv_customBgImage'); + root.style.setProperty('--bg-image', 'none'); + if (uploadBg) uploadBg.value = ""; + + const decoded = decodeURIComponent(code.trim()); + const data = decoded.split('|'); + if (data.length < 29) { alert("Mã cấu hình không hợp lệ!"); return; } + + const setV = (id, v) => { const el = document.getElementById(id); if (el) el.value = v; }; + const setC = (id, v) => { const el = document.getElementById(id); if (el) el.checked = v; }; + + setV('val-bg-main', data[0]); setV('val-text-color', data[1]); + setLayoutMode(data[2]); setV('val-list-bg', data[3]); + setV('val-list-text', data[4]); setV('val-list-svg', data[5]); + setV('val-theme-frame', data[6]); + setV('val-frame-size', data[7]); setV('val-svg-size', data[8]); + setV('val-svg-opacity', data[9]); setV('val-frame-radius', data[10]); + setV('val-frame-color', data[11]); setV('val-svg-color', data[12]); + setC('toggle-hide-labels', data[13] === '1'); setV('val-title-size', data[14]); + setV('val-title-spacing', data[15]); setC('toggle-list-frame', data[16] === '1'); + setV('val-icon-size', data[17]); setV('val-icon-spacing', data[18]); + setV('val-list-bg-opacity', data[19]); setV('val-frame-bg-opacity', data[20]); + setV('val-media-width', data[21]); setV('val-media-bg-opacity', data[22]); + setV('val-media-btn-size', data[23]); setV('val-media-bg-color', data[24]); + setV('val-media-btn-color', data[25]); setV('val-media-svg-color', data[26]); + setC('toggle-glass', data[27] === '1'); setV('val-popup-anim', data[28]); + + document.querySelectorAll('.shadow-switch').forEach(chk => { + chk.checked = false; + const drw = document.getElementById(`drawer-${chk.value}`); + if (drw) drw.classList.remove('active'); + }); - if (data[29]) { - const shadows = data[29].split('~'); - shadows.forEach(sh => { - const p = sh.split('*'); - if (p.length > 1) { - const sId = p[0]; const shadowSwitch = document.querySelector(`input[name="active_shadow"][value="${sId}"]`); - if (shadowSwitch) { - shadowSwitch.checked = true; const drw = document.getElementById(`drawer-${sId}`); - if (drw) { - drw.classList.add('active'); - drw.querySelector('.s-x').value = p[1]; drw.querySelector('.s-y').value = p[2]; - drw.querySelector('.s-b').value = p[3]; drw.querySelector('.s-s').value = p[4]; - drw.querySelector('.s-c').value = p[5]; if (p[6]) drw.querySelector('.s-o').value = p[6]; + if (data[29]) { + const shadows = data[29].split('~'); + shadows.forEach(sh => { + const p = sh.split('*'); + if (p.length > 1) { + const sId = p[0]; + const shadowSwitch = document.querySelector(`input[name="active_shadow"][value="${sId}"]`); + if (shadowSwitch) { + shadowSwitch.checked = true; + const drw = document.getElementById(`drawer-${sId}`); + if (drw) { + drw.classList.add('active'); + drw.querySelector('.s-x').value = p[1]; + drw.querySelector('.s-y').value = p[2]; + drw.querySelector('.s-b').value = p[3]; + drw.querySelector('.s-s').value = p[4]; + drw.querySelector('.s-c').value = p[5]; + if (p[6]) drw.querySelector('.s-o').value = p[6]; + } } } - } - }); + }); + } + + if (data[30] !== undefined) setC('toggle-media-theme', data[30] === '1'); + if (data[31] !== undefined) setC('toggle-theme-dot', data[31] === '1'); + else setC('toggle-theme-dot', true); + if (data[32] !== undefined) setV('val-thumb-size', data[32]); + if (data[33] !== undefined) setV('val-thumb-color', data[33]); + if (data[34] !== undefined) setV('val-thumb-theme', data[34]); + + updateLiveVariables(true); + } catch(e) { + console.error(e); + alert("Lỗi đọc mã cấu hình! Vui lòng thử lại."); } + } - if (data[30] !== undefined) document.getElementById('toggle-media-theme').checked = data[30] === '1'; - if (data[31] !== undefined) document.getElementById('toggle-theme-dot').checked = data[31] === '1'; else document.getElementById('toggle-theme-dot').checked = true; - if (data[32] !== undefined) document.getElementById('val-thumb-size').value = data[32]; - if (data[33] !== undefined) document.getElementById('val-thumb-color').value = data[33]; - if (data[34] !== undefined) document.getElementById('val-thumb-theme').value = data[34]; + const btnExport = document.getElementById('btn-export'); + if (btnExport) { + btnExport.addEventListener('click', (e) => { + e.preventDefault(); + const code = packConfig(); + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(code).then(() => { + alert("Đã sao chép mã cấu hình thành công!"); + }).catch(() => { prompt("Sao chép mã cấu hình bên dưới:", code); }); + } else { + prompt("Sao chép mã cấu hình bên dưới:", code); + } + }); + } - updateLiveVariables(true); - } catch(e) { console.error(e); alert("Lỗi đọc mã cấu hình! Vui lòng thử lại."); } - } + const btnImport = document.getElementById('btn-import'); + if (btnImport) { + btnImport.addEventListener('click', (e) => { + e.preventDefault(); + const code = prompt("📥 Dán mã cấu hình vào đây:"); + if (code) { unpackConfig(code); resetPresetToCustom(); } + }); + } - if (document.getElementById('btn-export')) { - document.getElementById('btn-export').addEventListener('click', (e) => { - e.preventDefault(); const code = packConfig(); - if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(code).then(() => { alert("Đã sao chép mã cấu hình thành công!"); }).catch(() => { prompt("Sao chép mã cấu hình bên dưới:", code); }); - else prompt("Sao chép mã cấu hình bên dưới:", code); - }); - } + const btnResetDefault = document.getElementById('btn-reset-default'); + if (btnResetDefault) { + btnResetDefault.addEventListener('click', (e) => { + e.preventDefault(); + if (confirm('⚠️ Khôi phục tất cả cài đặt về mặc định?\n\nThao tác này sẽ xóa toàn bộ tuỳ chỉnh, hình nền, theme hiện tại và KHÔNG THỂ hoàn tác.\n\nBạn có chắc chắn?')) { + try { + const keysToRemove = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith('sttv_')) keysToRemove.push(key); + } + keysToRemove.forEach(k => localStorage.removeItem(k)); + } catch(err) { console.warn(err); } + location.reload(); + } + }); + } - if (document.getElementById('btn-import')) { - document.getElementById('btn-import').addEventListener('click', (e) => { - e.preventDefault(); const code = prompt("📥 Dán mã cấu hình vào đây:"); - if (code) { unpackConfig(code); resetPresetToCustom(); } - }); - } + function updateLiveVariables(saveNow = false) { + root.style.setProperty('--bg-main', g('val-bg-main')); + root.style.setProperty('--text-color', g('val-text-color')); + root.style.setProperty('--frame-size', g('val-frame-size') + 'px'); + root.style.setProperty('--svg-size', g('val-svg-size') + 'px'); + root.style.setProperty('--svg-color', g('val-svg-color')); + root.style.setProperty('--svg-opacity', g('val-svg-opacity') / 100); + + const rawRadius = g('val-frame-radius'); + root.style.setProperty('--frame-border-radius', rawRadius + '%'); + root.style.setProperty('--list-border-radius', rawRadius + 'px'); + + root.style.setProperty('--title-size', g('val-title-size') + 'px'); + root.style.setProperty('--title-spacing', g('val-title-spacing') + 'px'); + root.style.setProperty('--icon-font-size', g('val-icon-size') + 'px'); + root.style.setProperty('--icon-spacing', g('val-icon-spacing') + 'px'); + root.style.setProperty('--label-display', c('toggle-hide-labels') ? 'none' : 'block'); + + const currentLayout = localStorage.getItem('sttv_layoutMode') || 'grid'; + if (btnLayoutList && btnLayoutGrid && mainContainer) { + if (currentLayout === 'list') { + btnLayoutList.classList.add('active'); + btnLayoutGrid.classList.remove('active'); + mainContainer.classList.add('list-mode'); + mainContainer.classList.remove('grid-mode'); + } else { + btnLayoutGrid.classList.add('active'); + btnLayoutList.classList.remove('active'); + mainContainer.classList.add('grid-mode'); + mainContainer.classList.remove('list-mode'); + } + } - if (document.getElementById('btn-reset-default')) { - document.getElementById('btn-reset-default').addEventListener('click', (e) => { - e.preventDefault(); - if (confirm('⚠️ Khôi phục tất cả cài đặt về mặc định?\n\nThao tác này sẽ xóa toàn bộ tuỳ chỉnh, hình nền, theme hiện tại và KHÔNG THỂ hoàn tác.\n\nBạn có chắc chắn?')) { - try { - const keysToRemove = []; - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - if (key && key.startsWith('sttv_')) keysToRemove.push(key); - } - keysToRemove.forEach(k => localStorage.removeItem(k)); - } catch(err) { console.warn(err); } - location.reload(); + if (mainContainer) { + if (c('toggle-list-frame')) mainContainer.classList.add('list-frame-active'); + else mainContainer.classList.remove('list-frame-active'); } - }); - } - function updateLiveVariables(saveNow = false) { - root.style.setProperty('--bg-main', document.getElementById('val-bg-main').value); - root.style.setProperty('--text-color', document.getElementById('val-text-color').value); - root.style.setProperty('--frame-size', document.getElementById('val-frame-size').value + 'px'); - root.style.setProperty('--svg-size', document.getElementById('val-svg-size').value + 'px'); - root.style.setProperty('--svg-color', document.getElementById('val-svg-color').value); - root.style.setProperty('--svg-opacity', document.getElementById('val-svg-opacity').value / 100); - - const rawRadius = document.getElementById('val-frame-radius').value; - root.style.setProperty('--frame-border-radius', rawRadius + '%'); - root.style.setProperty('--list-border-radius', rawRadius + 'px'); - - root.style.setProperty('--title-size', document.getElementById('val-title-size').value + 'px'); - root.style.setProperty('--title-spacing', document.getElementById('val-title-spacing').value + 'px'); - root.style.setProperty('--icon-font-size', document.getElementById('val-icon-size').value + 'px'); - root.style.setProperty('--icon-spacing', document.getElementById('val-icon-spacing').value + 'px'); - root.style.setProperty('--label-display', document.getElementById('toggle-hide-labels').checked ? 'none' : 'block'); - - const currentLayout = localStorage.getItem('sttv_layoutMode') || 'grid'; - const btnList = document.getElementById('btn-layout-list'); const btnGrid = document.getElementById('btn-layout-grid'); - if (currentLayout === 'list') { btnList.classList.add('active'); btnGrid.classList.remove('active'); mainContainer.classList.add('list-mode'); mainContainer.classList.remove('grid-mode'); } - else { btnGrid.classList.add('active'); btnList.classList.remove('active'); mainContainer.classList.add('grid-mode'); mainContainer.classList.remove('list-mode'); } - - const isListFrame = document.getElementById('toggle-list-frame').checked; - if (isListFrame) mainContainer.classList.add('list-frame-active'); else mainContainer.classList.remove('list-frame-active'); - - const listBgHex = document.getElementById('val-list-bg').value; - const listBgOpacity = document.getElementById('val-list-bg-opacity').value; - root.style.setProperty('--list-bg-color', listBgHex); - root.style.setProperty('--list-bg-rgba', hexToRgba(listBgHex, listBgOpacity)); - root.style.setProperty('--list-text-color', document.getElementById('val-list-text').value); - root.style.setProperty('--list-svg-color', document.getElementById('val-list-svg').value); - - const frameSelect = document.getElementById('val-theme-frame').value; - const frameColorRgba = hexToRgba(document.getElementById('val-frame-color').value, document.getElementById('val-frame-bg-opacity').value); - if (frameSelect === 'none') { root.style.setProperty('--frame-bg', 'none'); root.style.setProperty('--frame-bg-color', frameColorRgba); } - else { root.style.setProperty('--frame-bg', `url('../${frameSelect}')`); root.style.setProperty('--frame-bg-color', 'transparent'); } - - if (document.getElementById('toggle-glass').checked) mainContainer.classList.add('glass-active'); else mainContainer.classList.remove('glass-active'); - - root.style.setProperty('--media-bg-rgb', hexToRgb(document.getElementById('val-media-bg-color').value)); - root.style.setProperty('--media-bg-opacity', document.getElementById('val-media-bg-opacity').value / 100); - root.style.setProperty('--media-width', document.getElementById('val-media-width').value + '%'); - root.style.setProperty('--media-btn-color', document.getElementById('val-media-btn-color').value); - root.style.setProperty('--media-svg-color', document.getElementById('val-media-svg-color').value); - const mediaBtnSize = document.getElementById('val-media.id}-btn"]-size`); -').value; - root.style.setProperty('--media-btn-size', mediaBtnSize + 'px'); - root.style.setProperty('--media-btn-play', (parseInt(mediaBtnSize) + 15) + 'px'); - - root.style.setProperty('--thumb-size', document.getElementById('val-thumb-size'). value + 'px'); - root.style.setProperty('--thumb-color', document.getElementById('val-thumb-color').value); - const thumbThemeSelect = document.getElementById('val-thumb-theme').value; - if (thumbThemeSelect === 'none') root.style.setProperty('--thumb-bg-image', "url('../Theme/icon-dot.png')"); - else root.style.setProperty('--thumb-bg-image', `url('../${thumbThemeSelect}')`); - - const toggleMediaTheme = document.getElementById('toggle-media-theme'); - const toggleThemeDot = document.getElementById('toggle-theme-dot'); - const btnThumbThemeEl = document.getElementById('btn-thumb-theme'); - const thumbColorItem = document.getElementById('val-thumb-color').parentElement; - const mediaWidget = document.querySelector('.media-player-widget'); - - if (mediaWidget) { - if (toggleMediaTheme && toggleMedia constTheme.checked) mediaWidget.classList.add('theme-synced'); - else mediaWidget.classList.remove('theme-synced'); - - if (toggleThemeDot && toggleThemeDot.checked) { - mediaWidget.classList.remove('pure-css-dots'); - if(btnThumbThemeEl) btnThumbThemeEl.classList.remove('disabled'); - if(thumbColorItem) { thumbColorItem.style.opacity = '0.5'; thumbColorItem.style.pointerEvents = 'none'; } - } else { - mediaWidget.classList.add('pure-css-dots'); - if(btnThumbThemeEl) btnThumbThemeEl.classList.add('disabled'); - if(thumbColorItem) { thumbColorItem.style.opacity = '1'; thumbColorItem.style.pointerEvents = 'auto'; } + const listBgHex = g('val-list-bg'); + const listBgOpacity = g('val-list-bg-opacity'); + root.style.setProperty('--list-bg-color', listBgHex); + root.style.setProperty('--list-bg-rgba', hexToRgba(listBgHex, listBgOpacity)); + root.style.setProperty('--list-text-color', g('val-list-text')); + root.style.setProperty('--list-svg-color', g('val-list-svg')); + + const frameSelect = g('val-theme-frame'); + const frameColorRgba = hexToRgba(g('val-frame-color'), g('val-frame-bg-opacity')); + if (frameSelect === 'none' || !frameSelect) { + root.style.setProperty('--frame-bg', 'none'); + root.style.setProperty('--frame-bg-color', frameColorRgba); + } else { + root.style.setProperty('--frame-bg', `url('../${frameSelect}')`); + root.style.setProperty('--frame-bg-color', 'transparent'); + } + + if (mainContainer) { + if (c('toggle-glass')) mainContainer.classList.add('glass-active'); + else mainContainer.classList.remove('glass-active'); } - } - updateShadow(); - if (saveNow) saveSettingsToLocal(); - } + root.style.setProperty('--media-bg-rgb', hexToRgb(g('val-media-bg-color'))); + root.style.setProperty('--media-bg-opacity', g('val-media-bg-opacity') / 100); + root.style.setProperty('--media-width', g('val-media-width') + '%'); + root.style.setProperty('--media-btn-color', g('val-media-btn-color')); + root.style.setProperty('--media-svg-color', g('val-media-svg-color')); + const mediaBtnSize = g('val-media-btn-size'); + root.style.setProperty('--media-btn-size', mediaBtnSize + 'px'); + root.style.setProperty('--media-btn-play', (parseInt(mediaBtnSize) + 15) + 'px'); + + root.style.setProperty('--thumb-size', g('val-thumb-size') + 'px'); + root.style.setProperty('--thumb-color', g('val-thumb-color')); + const thumbThemeSelect = g('val-thumb-theme'); + if (thumbThemeSelect === 'none') root.style.setProperty('--thumb-bg-image', "url('../Theme/icon-dot.png')"); + else root.style.setProperty('--thumb-bg-image', `url('../${thumbThemeSelect}')`); + + const toggleMediaTheme = document.getElementById('toggle-media-theme'); + const toggleThemeDot = document.getElementById('toggle-theme-dot'); + const btnThumbThemeEl = document.getElementById('btn-thumb-theme'); + const thumbColorEl = document.getElementById('val-thumb-color'); + const thumbColorItem = thumbColorEl ? thumbColorEl.parentElement : null; + const mediaWidget = document.querySelector('.media-player-widget'); + + if (mediaWidget) { + if (toggleMediaTheme && toggleMediaTheme.checked) mediaWidget.classList.add('theme-synced'); + else mediaWidget.classList.remove('theme-synced'); + + if (toggleThemeDot && toggleThemeDot.checked) { + mediaWidget.classList.remove('pure-css-dots'); + if (btnThumbThemeEl) btnThumbThemeEl.classList.remove('disabled'); + if (thumbColorItem) { thumbColorItem.style.opacity = '0.5'; thumbColorItem.style.pointerEvents = 'none'; } + } else { + mediaWidget.classList.add('pure-css-dots'); + if (btnThumbThemeEl) btnThumbThemeEl.classList.add('disabled'); + if (thumbColorItem) { thumbColorItem.style.opacity = '1'; thumbColorItem.style.pointerEvents = 'auto'; } + } + } - function loadSettingsFromLocal() { - const safeSet = (id, key, fallback, isCheck = false) => { - const el = document.getElementById(id); if (!el) return; - const val = localStorage.getItem('sttv_' + key); - if (isCheck) el.checked = val === 'true' ? true : (val === null ? fallback : false); - else el.value = val !== null ? val : fallback; - }; + updateShadow(); + if (saveNow) saveSettingsToLocal(); + } - safeSet('val-bg-main', 'bgMain', '#121b22'); safeSet('val-text-color', 'textColor', '#ffffff'); - safeSet('val-list-bg', 'listBg', '#1a1a1a'); safeSet('val-list-bg-opacity', 'listBgOpacity', '10'); - safeSet('val-list-text', 'listText', '#ffffff'); safeSet('val-list-svg', 'listSvg', '#ffffff'); - - if (document.getElementById('val-theme-frame')) document.getElementById('val-theme-frame').value = localStorage.getItem('sttv_themeFrame') || 'none'; - presetSelect.value = localStorage.getItem('sttv_activePreset') || 'none'; - - const savedAnim = localStorage.getItem('sttv_popupAnim') || 'default'; - if (document.getElementById('val-popup-anim')) document.getElementById('val-popup-anim').value = savedAnim; - if (savedAnim !== 'default') drawerEl.classList.add(savedAnim); - - safeSet('val-frame-size', 'frameSize', '60'); safeSet('val-svg-size', 'svgSize', '28'); - safeSet('val-svg-opacity', 'svgOpacity', '100'); safeSet('val-svg-color', 'svgColor', '#ffffff'); - safeSet('val-frame-radius', 'frameRadius', '22'); safeSet('val-frame-color', 'frameColor', '#000000'); - safeSet('val-frame-bg-opacity', 'frameBgOpacity', '100'); - - safeSet('toggle-hide-labels', 'hideLabels', false, true); safeSet('toggle-list-frame', 'listFrame', false, true); - safeSet('val-title-size', 'titleSize', '22'); safeSet('val-title-spacing', 'titleSpacing', '0.5'); - safeSet('val-icon-size', 'iconSize', '14'); safeSet('val-icon-spacing', 'iconSpacing', '0'); - safeSet('toggle-glass', 'glassMode', false, true); safeSet('toggle-audio', 'audioFeedback', false, true); - safeSet('toggle-parallax', 'parallax', false, true); - - safeSet('val-media-width', 'mediaWidth', '90'); safeSet('val-media-bg-opacity', 'mediaBgOpacity', '3'); - safeSet('val-media-btn-size', 'mediaBtnSize', '50'); safeSet('val-media-bg-color', 'mediaBgColor', '#ffffff'); - safeSet('val-media-btn-color', 'mediaBtnColor', '#ffffff'); safeSet('val-media-svg-color', 'mediaSvgColor', '#ffffff'); - safeSet('toggle-media-theme', 'mediaThemeSync', false, true); - - safeSet('toggle-theme-dot', 'themeDot', true, true); - safeSet('val-thumb-size', 'thumbSize', '25'); - safeSet('val-thumb-color', 'thumbColor', '#ffffff'); - if (document.getElementById('val-thumb-theme')) document.getElementById('val-thumb-theme').value = localStorage.getItem('sttv_thumbTheme') || 'none'; + function loadSettingsFromLocal() { + const safeSet = (id, key, fallback, isCheck = false) => { + const el = document.getElementById(id); + if (!el) return; + const val = localStorage.getItem('sttv_' + key); + if (isCheck) el.checked = val === 'true' ? true : (val === null ? fallback : false); + else el.value = val !== null ? val : fallback; + }; - const savedShadowConfig = localStorage.getItem('sttv_shadowConfig'); - if (savedShadowConfig) { - try { - const shadowState = JSON.parse(savedShadowConfig); - SHADOW_MODES.forEach(mode => { - if (shadowState[mode.id]) { - const item = shadowState[mode.id]; - const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode drawer = document.getElementById(`drawer-${mode.id}`); - if (checkbox && drawer) { - checkbox.checked = item.active; - if (item.active) drawer.classList.add('active'); else drawer.classList.remove('active'); - drawer.querySelector('.s-x').value = item.x; drawer.querySelector('.s-y').value = item.y; - drawer.querySelector('.s-b').value = item.b; drawer.querySelector('.s-s').value = item.s; - drawer.querySelector('.s-c').value = item.c; drawer.querySelector('.s-o').value = item.o; + safeSet('val-bg-main', 'bgMain', '#121b22'); + safeSet('val-text-color', 'textColor', '#ffffff'); + safeSet('val-list-bg', 'listBg', '#1a1a1a'); + safeSet('val-list-bg-opacity', 'listBgOpacity', '10'); + safeSet('val-list-text', 'listText', '#ffffff'); + safeSet('val-list-svg', 'listSvg', '#ffffff'); + + const vtf = document.getElementById('val-theme-frame'); + if (vtf) vtf.value = localStorage.getItem('sttv_themeFrame') || 'none'; + if (presetSelect) presetSelect.value = localStorage.getItem('sttv_activePreset') || 'none'; + + const savedAnim = localStorage.getItem('sttv_popupAnim') || 'default'; + if (popupAnimSelect) popupAnimSelect.value = savedAnim; + if (savedAnim !== 'default' && drawerEl) drawerEl.classList.add(savedAnim); + + safeSet('val-frame-size', 'frameSize', '60'); + safeSet('val-svg-size', 'svgSize', '28'); + safeSet('val-svg-opacity', 'svgOpacity', '100'); + safeSet('val-svg-color', 'svgColor', '#ffffff'); + safeSet('val-frame-radius', 'frameRadius', '22'); + safeSet('val-frame-color', 'frameColor', '#000000'); + safeSet('val-frame-bg-opacity', 'frameBgOpacity', '100'); + + safeSet('toggle-hide-labels', 'hideLabels', false, true); + safeSet('toggle-list-frame', 'listFrame', false, true); + safeSet('val-title-size', 'titleSize', '22'); + safeSet('val-title-spacing', 'titleSpacing', '0.5'); + safeSet('val-icon-size', 'iconSize', '14'); + safeSet('val-icon-spacing', 'iconSpacing', '0'); + safeSet('toggle-glass', 'glassMode', false, true); + safeSet('toggle-audio', 'audioFeedback', false, true); + safeSet('toggle-parallax', 'parallax', false, true); + + safeSet('val-media-width', 'mediaWidth', '90'); + safeSet('val-media-bg-opacity', 'mediaBgOpacity', '3'); + safeSet('val-media-btn-size', 'mediaBtnSize', '50'); + safeSet('val-media-bg-color', 'mediaBgColor', '#ffffff'); + safeSet('val-media-btn-color', 'mediaBtnColor', '#ffffff'); + safeSet('val-media-svg-color', 'mediaSvgColor', '#ffffff'); + safeSet('toggle-media-theme', 'mediaThemeSync', false, true); + + safeSet('toggle-theme-dot', 'themeDot', true, true); + safeSet('val-thumb-size', 'thumbSize', '25'); + safeSet('val-thumb-color', 'thumbColor', '#ffffff'); + const vtt = document.getElementById('val-thumb-theme'); + if (vtt) vtt.value = localStorage.getItem('sttv_thumbTheme') || 'none'; + + const savedShadowConfig = localStorage.getItem('sttv_shadowConfig'); + if (savedShadowConfig) { + try { + const shadowState = JSON.parse(savedShadowConfig); + SHADOW_MODES.forEach(mode => { + if (shadowState[mode.id]) { + const item = shadowState[mode.id]; + const checkbox = document.querySelector(`input[name="active_shadow"][value="${mode.id}"]`); + const drawer = document.getElementById(`drawer-${mode.id}`); + if (checkbox && drawer) { + checkbox.checked = item.active; + if (item.active) drawer.classList.add('active'); + else drawer.classList.remove('active'); + drawer.querySelector('.s-x').value = item.x; + drawer.querySelector('.s-y').value = item.y; + drawer.querySelector('.s-b').value = item.b; + drawer.querySelector('.s-s').value = item.s; + drawer.querySelector('.s-c').value = item.c; + drawer.querySelector('.s-o').value = item.o; + } } - } - }); - } catch(e) {} - } + }); + } catch(e) {} + } - const initLayout = localStorage.getItem('sttv_layoutMode') || 'grid'; - if (initLayout === 'list') { document.getElementById('btn-layout-list').classList.add('active'); document.getElementById('btn-layout-grid').classList.remove('active'); } - else { document.getElementById('btn-layout-grid').classList.add('active'); document.getElementById('btn-layout-list').classList.remove('active'); } + const initLayout = localStorage.getItem('sttv_layoutMode') || 'grid'; + if (btnLayoutList && btnLayoutGrid) { + if (initLayout === 'list') { + btnLayoutList.classList.add('active'); + btnLayoutGrid.classList.remove('active'); + } else { + btnLayoutGrid.classList.add('active'); + btnLayoutList.classList.remove('active'); + } + } - const savedBg = localStorage.getItem('sttv_customBgImage'); - if (savedBg) root.style.setProperty('--bg-image', `url('${savedBg}')`); - } + const savedBg = localStorage.getItem('sttv_customBgImage'); + if (savedBg) root.style.setProperty('--bg-image', `url('${savedBg}')`); + } - loadSettingsFromLocal(); - updateLiveVariables(true); + loadSettingsFromLocal(); + updateLiveVariables(true); - document.addEventListener("visibilitychange", () => { if (document.visibilityState === 'hidden') saveSettingsToLocal(); }); - window.addEventListener("beforeunload", saveSettingsToLocal); + document.addEventListener("visibilitychange", () => { + if (document.visibilityState === 'hidden') saveSettingsToLocal(); + }); + window.addEventListener("beforeunload", saveSettingsToLocal); + } catch(err) { + console.error('Theme.js crashed:', err); + if (window.__dismissIntro) window.__dismissIntro(); + } }); \ No newline at end of file From 2254c5af6a62bd77b2c6fb0a214bc29724447452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:42:48 +0700 Subject: [PATCH 07/23] Update style.css --- CSS/style.css | 258 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 171 insertions(+), 87 deletions(-) diff --git a/CSS/style.css b/CSS/style.css index fda8b1f..c053202 100644 --- a/CSS/style.css +++ b/CSS/style.css @@ -65,7 +65,19 @@ body.standard-mode .dev-only { display: none !important; } ============================================ */ .slider-item { position: relative; display: flex; align-items: center; margin-bottom: 8px; width: 100%; transition: opacity 0.3s ease; } .slider-item input[type=range] { flex: 1; width: 100% !important; } -.slider-label { width: 105px; font-size: 12px; opacity: 0.8; flex-shrink: 0; } +.slider-label { + width: 110px; + font-size: 12px; + opacity: 0.85; + flex-shrink: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 6px; +} +@media (max-width: 380px) { + .slider-label { width: 95px; font-size: 11px; } +} .slider-tooltip { position: fixed; background: #007aff; color: #ffffff; font-size: 11px; font-weight: bold; padding: 4px 8px; border-radius: 8px; opacity: 0; transform: translateX(-50%) translateY(5px); transition: opacity 0.15s ease, transform 0.15s ease; pointer-events: none; z-index: 999999; white-space: nowrap; box-shadow: 0 2px 8px rgba(0,0,0,0.4); } .slider-tooltip.show { opacity: 1; transform: translateX(-50%) translateY(0); } @@ -149,17 +161,12 @@ input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 6px; bac filter: none !important; } -/* ============================================ - THEME DOT PLUS BUTTON - ============================================ */ .btn-plus-theme { width: 26px; height: 26px; border-radius: 50%; background: #007aff; color: white; border: none; font-size: 18px; font-weight: 300; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0, 122, 255, 0.4); } -.btn-plus-theme.disabled { - background: #8e8e93; box-shadow: none; opacity: 0.5; pointer-events: none; -} +.btn-plus-theme.disabled { background: #8e8e93; box-shadow: none; opacity: 0.5; pointer-events: none; } .btn-plus-theme span { margin-top: -2px; } /* ============================================ @@ -198,29 +205,11 @@ input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 6px; bac will-change: right, opacity; display: flex; flex-direction: column; color: white; overflow: hidden; } .settings-drawer.open { right: 15px; } -.settings-drawer.adjusting { background: rgba(30, 30, 30, 0.1) !important; opacity: 0.15 !important; backdrop-filter: blur(0px) !important; -webkit-backdrop-filter: blur(0px) !important; box-shadow: none !important; } - -.settings-body { padding: 20px 20px 60px 20px; overflow-y: auto; flex: 1; } -.setting-group { margin-bottom: 20px; display: flex; flex-direction: column; gap: 8px; } -.highlight5-box { background: rgba(255,255,255,px0.05); padding: 15px; border-radius:; - 12px; border: 1px solid rgba( 255,255,255,0 line.1); } -.feature-toggles { display-height: flex; flex-direction: column; gap: 10px; margin-top: 10px; font-size: 13px; } -.feature-toggles label { display: flex; justify-content: space-between; align-items: center; } -.section-title { margin: 20px 0 10px; font-size: 16px; color: var(--accent-color); border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 5px; } -.slider-item input[type="color"] { height: 28px; padding: 0; border: none; background: none; border-radius: 5px; cursor: pointer; } -.layout-buttons { display: flex; gap: 10px; } -.layout-btn { flex: 1; padding: 10px 0; background: rgba(255,255,255,0.1); border: 1px solid transparent; color: white; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.2s; } -.layout-btn.active { background: var(--accent-color); font-weight: bold; } -.config-actions { display: flex; gap: 5px; margin-bottom: 20px; } -.action-btn { flex: 1; padding: 8px; background: rgba(255,255,255,0.15); border: none; color: white; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 5px; font-size: 13px; font-weight: 500; } -.shadow-item { border: 1px solid rgba(255,255,255,0.1); border-radius: 12px; margin-bottom: 10px; overflow: hidden; } -.shadow-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; background: rgba(0,0,0,0.2); } -.shadow-drawer { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; padding: 0 15px; background: rgba(0,0,0,0.1); } -.shadow-drawer.active { max-height: 400px; padding: 15px; } -select { width: 100%; padding: 10px; border-radius: 8px; background: rgba(0,0,0,0.3); color: white; border: 1px solid rgba(255,255,255,0.2); font-size: 14px; } +/* ⭐ FIX #6: Panel mờ 0.4 thay vì 0.15 để user vẫn thấy preview */ +.settings-drawer.adjusting { background: rgba(30, 30, 30, 0.35) !important; opacity: 0.4 !important; backdrop-filter: blur(2px) !important; -webkit-backdrop-filter: blur(2px) !important; box-shadow: none !important; } /* ============================================ - ⭐ SETTINGS HEADER MỚI - Nút X / Pill CÀI ĐẶT / Nút Info + ⭐ SETTINGS HEADER ============================================ */ .settings-header { padding: 12px 15px; @@ -257,9 +246,7 @@ select { width: 100%; padding: 10px; border-radius: 8px; background: rgba(0,0,0, font-family: Georgia, serif; box-shadow: 0 2px 10px rgba(0, 122, 255, 0.55), inset 0 1px 0 rgba(255,255,255,0.3); } -.settings-header .header-btn.info-header-btn:active { - background: linear-gradient(135deg, #0066dd, #0044aa); -} +.settings-header .header-btn.info-header-btn:active { background: linear-gradient(135deg, #0066dd, #0044aa); } .settings-header .header-title-pill { position: absolute; left: 50%; @@ -279,12 +266,99 @@ select { width: 100%; padding: 10px; border-radius: 8px; background: rgba(0,0,0, z-index: 1; } +/* ============================================ + ⭐ TAB BAR - NEW + ============================================ */ +.settings-tabs-bar { + display: flex; + gap: 4px; + padding: 10px 12px; + background: rgba(0,0,0,0.25); + border-bottom: 1px solid rgba(255,255,255,0.06); + overflow-x: auto; + scrollbar-width: none; + -ms-overflow-style: none; + flex-shrink: 0; +} +.settings-tabs-bar::-webkit-scrollbar { display: none; } +.settings-tab { + flex: 1; + min-width: 66px; + padding: 9px 6px; + border: none; + background: rgba(255,255,255,0.06); + color: rgba(255,255,255,0.6); + border-radius: 10px; + font-size: 11px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + font-family: -apple-system, sans-serif; + -webkit-tap-highlight-color: transparent; + text-align: center; +} +.settings-tab:active { transform: scale(0.96); } +.settings-tab.active { + background: linear-gradient(135deg, #007aff, #0055cc); + color: white; + box-shadow: 0 3px 10px rgba(0, 122, 255, 0.45); + font-weight: 700; +} + +.settings-tab-content { display: none; } +.settings-tab-content.active { display: block; animation: tabFadeIn 0.25s ease-out; } +@keyframes tabFadeIn { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ============================================ + SETTINGS BODY & GROUPS + ============================================ */ +.settings-body { padding: 16px 18px 60px 18px; overflow-y: auto; flex: 1; } +.setting-group { margin-bottom: 18px; display: flex; flex-direction: column; gap: 8px; } +.highlight-box { background: rgba(255,255,255,0.05); padding: 15px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1); } +.feature-toggles { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; font-size: 13px; } +.feature-toggles label { display: flex; justify-content: space-between; align-items: center; } +.section-title { margin: 20px 0 10px; font-size: 16px; color: var(--accent-color); border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 5px; } +.slider-item input[type="color"] { height: 28px; padding: 0; border: none; background: none; border-radius: 5px; cursor: pointer; } +.layout-buttons { display: flex; gap: 10px; } +.layout-btn { flex: 1; padding: 10px 0; background: rgba(255,255,255,0.1); border: 1px solid transparent; color: white; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.2s; } +.layout-btn.active { background: var(--accent-color); font-weight: bold; } +.config-actions { display: flex; gap: 5px; margin-bottom: 8px; } +.action-btn { flex: 1; padding: 8px; background: rgba(255,255,255,0.15); border: none; color: white; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 5px; font-size: 13px; font-weight: 500; } + +/* ============================================ + ⭐ FIX #5: SHADOW HEADER TO HƠN + ============================================ */ +.shadow-item { border: 1px solid rgba(255,255,255,0.1); border-radius: 12px; margin-bottom: 10px; overflow: hidden; } +.shadow-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 18px; + min-height: 52px; + background: rgba(0,0,0,0.2); +} +.shadow-header span { font-size: 14px; font-weight: 500; } +.shadow-header input[type=checkbox] { + width: 24px; + height: 24px; + cursor: pointer; + accent-color: #007aff; + flex-shrink: 0; +} +.shadow-drawer { max-height: 0; overflow: hidden; transition: max-height 0.35s ease; padding: 0 15px; background: rgba(0,0,0,0.1); } +.shadow-drawer.active { max-height: 500px; padding: 15px; } + +select { width: 100%; padding: 10px; border-radius: 8px; background: rgba(0,0,0,0.3); color: white; border: 1px solid rgba(255,255,255,0.2); font-size: 14px; } + /* ============================================ ⭐ NÚT KHÔI PHỤC MẶC ĐỊNH ============================================ */ .config-actions-secondary { display: flex; - margin-top: -12px; margin-bottom: 20px; } .config-actions-secondary .action-btn { @@ -303,6 +377,58 @@ select { width: 100%; padding: 10px; border-radius: 8px; background: rgba(0,0,0, } .config-actions-secondary .action-btn.danger { width: 100%; } +/* ============================================ + ⭐ FIX #7: BẢNG MÀU NHANH - 5 SWATCH HÀNG NGANG + ============================================ */ +.color-swatch-row { + display: flex; + gap: 10px; + justify-content: space-between; + margin-top: 12px; + padding: 4px 2px; +} +.color-swatch { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + position: relative; +} +.color-swatch input[type=color] { + -webkit-appearance: none; + appearance: none; + width: 100%; + max-width: 48px; + height: 48px; + border: 2px solid rgba(255,255,255,0.18); + border-radius: 14px; + padding: 0; + cursor: pointer; + background: transparent; + overflow: hidden; + transition: all 0.2s ease; + box-shadow: 0 2px 8px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.15); +} +.color-swatch input[type=color]:active { + transform: scale(0.92); + border-color: #007aff; + box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.35); +} +.color-swatch input[type=color]::-webkit-color-swatch-wrapper { padding: 0; } +.color-swatch input[type=color]::-webkit-color-swatch { + border: none; + border-radius: 11px; +} +.swatch-label { + font-size: 10px; + font-weight: 600; + color: rgba(255,255,255,0.65); + letter-spacing: 0.3px; + white-space: nowrap; + text-align: center; +} + /* ============================================ MACOS MODAL WINDOWS ============================================ */ @@ -317,20 +443,9 @@ select { width: 100%; padding: 10px; border-radius: 8px; background: rgba(0,0,0, .max-dot { background: #27c93f; } .macos-body { padding: 25px 20px 30px 20px; text-align: center; } -/* ============================================ - ⭐ POPUP INFO MỚI - 3 PILL BUTTONS KÍNH MỜ - ============================================ */ -.info-body { - padding: 18px 16px 24px !important; - text-align: center; -} -.info-pill-tabs { - display: flex; - gap: 6px; - justify-content: center; - margin-bottom: 20px; - flex-wrap: nowrap; -} +/* INFO MODAL */ +.info-body { padding: 18px 16px 24px !important; text-align: center; } +.info-pill-tabs { display: flex; gap: 6px; justify-content: center; margin-bottom: 20px; flex-wrap: nowrap; } .info-pill { padding: 9px 15px; border-radius: 22px; @@ -360,57 +475,26 @@ select { width: 100%; padding: 10px; border-radius: 8px; background: rgba(0,0,0, } .info-content { text-align: center; } -.info-app-name { - font-size: 22px; - font-weight: 800; - color: #007aff; - margin: 0 0 10px; - letter-spacing: 0.3px; -} +.info-app-name { font-size: 22px; font-weight: 800; color: #007aff; margin: 0 0 10px; letter-spacing: 0.3px; } .info-version-row, .info-dev-row { - font-size: 13px; - color: #666; - margin-bottom: 8px; - display: flex; - justify-content: center; - align-items: center; - gap: 6px; - flex-wrap: wrap; + font-size: 13px; color: #666; margin-bottom: 8px; + display: flex; justify-content: center; align-items: center; gap: 6px; flex-wrap: wrap; } .info-version-value { - font-weight: 700; - color: #000; - background: rgba(0,0,0,0.07); - padding: 3px 10px; - border-radius: 8px; - font-size: 12.5px; -} -.info-dev-link { - color: #007aff; - font-weight: 700; - text-decoration: none; + font-weight: 700; color: #000; + background: rgba(0,0,0,0.07); padding: 3px 10px; border-radius: 8px; font-size: 12.5px; } +.info-dev-link { color: #007aff; font-weight: 700; text-decoration: none; } .info-dev-link:active { opacity: 0.7; } .info-desc-box, .info-guide-box { - text-align: left; - margin-top: 14px; - padding: 12px 14px; - background: rgba(0,0,0,0.04); - border-radius: 12px; - border: 1px solid rgba(0,0,0,0.06); + text-align: left; margin-top: 14px; padding: 12px 14px; + background: rgba(0,0,0,0.04); border-radius: 12px; border: 1px solid rgba(0,0,0,0.06); } .info-desc-title, .info-guide-title { - font-size: 13px; - font-weight: 700; - color: #007aff; - margin-bottom: 6px; - letter-spacing: 0.2px; + font-size: 13px; font-weight: 700; color: #007aff; margin-bottom: 6px; letter-spacing: 0.2px; } .info-desc-box p, .info-guide-box p { - font-size: 12.: 1.55; - color: #333; - margin: 0; - white-space: pre-line; + font-size: 12.5px; line-height: 1.55; color: #333; margin: 0; white-space: pre-line; } /* ============================================ From e783ac02cec869c330828d36be7542d2cd1e2041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:44:22 +0700 Subject: [PATCH 08/23] Update index.html --- index.html | 301 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 178 insertions(+), 123 deletions(-) diff --git a/index.html b/index.html index 3df955a..9f13cd7 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,7 @@ Trung tâm điều khiển - Sentechtipvn - + @@ -64,145 +64,186 @@
-
CÀI ĐẶT
+ + +
+ + + + + +
-
- - - -
- -
- -
+ +
+
+ + + +
-
- - +
+ +
+ +
+ + - - + + -
- - +
+ + +
-
-
- -
💫 Cỡ chữ chính
-
📐 Dãn chữ chính
-
-
💫 Cỡ chữ Icon
-
📐 Dãn chữ Icon
- -
+ +
+ +
+
+ + Nền +
+
+ + Chữ +
+
+ + Nền DS +
+
+ + Chữ DS +
+
+ + SVG DS +
+
+
-
- - - -
- -
+
+ + + +
+ +
-
-
+
+ +
+ + +
+
-
- -
- - +
+
Đậm nhạt màu nút
+ +
+ +
+ +
+
+
Black
+
Red
+
Green
+
Orange
+
+
+
+
-
- - - -
Đậm nhạt màu nút
- -
- -
- -
-
-
Black
-
Red
-
Green
-
Orange
-
+
+ +
+
+ +
💫 Cỡ chữ chính
+
📐 Dãn chữ chính
+
+
💫 Cỡ chữ Icon
+
📐 Dãn chữ Icon
+
-
- -
- -
Khung
-
Bo góc
-
SVG
-
Độ đậm SVG
-
Nền khung
-
Độ đậm nền
+ + +
+
+ + +
+ +
+ +
Khung
+
Bo góc
+
SVG
+
Độ đậm SVG
+
Nền khung
+
Độ đậm nền
+
- -
-
- -
Kích thước khung
-
Độ mờ khung
-
Cỡ nút bấm
- -
Kích thước núm
- -
Màu khung
-
Màu nút
-
Màu SVG
- -
🏳️‍🌈 Màu núm kéo
+ +
+
+ +
Kích thước khung
+
Độ mờ khung
+
Cỡ nút bấm
+
Kích thước núm
+
Màu khung
+
Màu nút
+
Màu SVG
+
🏳️‍🌈 Màu núm kéo
+ + +
+
- - - + +
+
-

🔘 ĐỔ BÓNG

-
-
@@ -235,7 +276,6 @@

🔘 ĐỔ BÓ

-
@@ -244,8 +284,6 @@

🔘 ĐỔ BÓ
Quick Control

- - -

Quick Control

-
Phiên bản 1.0
-
-
📝 Mô tả chức năng

Webclip điều khiển đa năng, tích hợp giao diện kính mờ và hệ thống Live Knobs tuỳ biến sống động chuyên nghiệp dành riêng cho iOS.

-
📖 Hướng dẫn sử dụng
-

1. Bấm vào nút bất kỳ để kích hoạt tính năng tương ứng (Wi-Fi, Bluetooth, VPN, Đèn pin...).

2. Nhấn vào biểu tượng ⚙️ ở góc dưới phải để mở bảng Cài đặt tuỳ chỉnh.

3. Chọn Theme có sẵn trong preset hoặc tự do kéo slider để tùy chỉnh màu sắc, kích thước khung, đổ bóng, kính mờ theo gu của bạn.

4. Vào mục Giao diện → Xuất để lưu mã cấu hình, Nhập để khôi phục lại khi cần. Nên sao lưu mã trước khi xóa webclip.

5. Slider âm lượng & độ sáng cần cài Shortcut tên "QuickControl" trong app Shortcuts để hoạt động.

+

1. Bấm vào nút bất kỳ để kích hoạt tính năng tương ứng (Wi-Fi, Bluetooth, VPN, Đèn pin...). 2. Nhấn vào biểu tượng ⚙️ ở góc dưới phải để mở bảng Cài đặt tuỳ chỉnh. 3. Chọn Theme có sẵn trong preset hoặc tự do kéo slider để tùy chỉnh màu sắc, kích thước khung, đổ bóng, kính mờ theo gu của bạn. 4. Vào mục Giao diện → Xuất để lưu mã cấu hình, Nhập để khôi phục lại khi cần. Nên sao lưu mã trước khi xóa webclip. 5. Slider âm lượng & độ sáng cần cài Shortcut tên "QuickControl" trong app Shortcuts để hoạt động.

- - - + + + + + \ No newline at end of file From 2ff9fec84a9260a97ec92b9a53c2481a80ab76a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:51:37 +0700 Subject: [PATCH 09/23] Update vi-VN.json --- Language/vi-VN.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Language/vi-VN.json b/Language/vi-VN.json index 9dd5342..37a4a5e 100644 --- a/Language/vi-VN.json +++ b/Language/vi-VN.json @@ -1,11 +1,11 @@ { "app_title": "Quick Control", "settings_title": "CÀI ĐẶT", - "tab_appearance": "🎨 Giao diện", - "tab_title": "✏️ Tiêu đề", - "tab_frame": "🖼️ Khung", - "tab_media": "🎵 Media", - "tab_shadow": "🌑 Bóng", + "tab_appearance": "Giao diện", + "tab_title": "Tiêu đề", + "tab_frame": "Khung", + "tab_media": "Media", + "tab_shadow": "Bóng", "btn_list": "Danh sách", "btn_grid": "Lưới", "btn_export": "Xuất", From 244b39daedb78a6f4feb81b725832143dd7993d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:52:34 +0700 Subject: [PATCH 10/23] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5b42f2..783f256 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ⚡ Quick Control — Trung Tâm Điều Khiển Đa Năng Đỉnh Cao Cho iOS +# ⚡ Quick Control — Trung Tâm Điều Khiển Cho iOS
From a84387c9a6a450ad2c04c1541fdc770cb47b3bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:52:59 +0700 Subject: [PATCH 11/23] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 783f256..e956c82 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ⚡ Quick Control — Trung Tâm Điều Khiển Cho iOS +## ⚡ Quick Control — Trung Tâm Điều Khiển Cho iOS
From d3eb4e3cb0493ff750e3aa686d87944c7f259ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:53:26 +0700 Subject: [PATCH 12/23] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e956c82..e016a9b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## ⚡ Quick Control — Trung Tâm Điều Khiển Cho iOS +## Quick Control — Trung Tâm Điều Khiển Cho iOS
From af2132f2e062a2627faebd91a7b147cff26ee01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:58:33 +0700 Subject: [PATCH 13/23] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e016a9b..bdf9494 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -## Quick Control — Trung Tâm Điều Khiển Cho iOS +

+ Banner Sentechtipsvn +

From 8355eed7943386278b1512129c20405a786be089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 12:00:21 +0700 Subject: [PATCH 14/23] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bdf9494..1764732 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ ## 📲 Trải Nghiệm Ngay Lập Tức • Lấy phím tắt **Tại đây** + • Thêm hồ sơ cấu hình [**Tại đây**](https://sentechtipsvn.github.io/Quick-Control/Quick-Control.mobileconfig) . Tận hưởng ngay trung tâm điều khiển đẳng cấp của riêng bạn! @@ -58,4 +59,4 @@

Phát triển bởi Sentechtipsvn

Mang sự hoàn mỹ đến gần hơn với hệ sinh thái Apple của bạn.

-
\ No newline at end of file +
From ab9ebc717bd79b3063cded19787f8e6bacc3e676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 12:19:58 +0700 Subject: [PATCH 15/23] Update vi-VN.json --- Language/vi-VN.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Language/vi-VN.json b/Language/vi-VN.json index 37a4a5e..9bfdafb 100644 --- a/Language/vi-VN.json +++ b/Language/vi-VN.json @@ -2,10 +2,10 @@ "app_title": "Quick Control", "settings_title": "CÀI ĐẶT", "tab_appearance": "Giao diện", - "tab_title": "Tiêu đề", - "tab_frame": "Khung", - "tab_media": "Media", - "tab_shadow": "Bóng", + "tab_title": "TIÊU ĐỀ", + "tab_frame": "KHUNG", + "tab_media": "NHẠC", + "tab_shadow": "ĐỔ BÓNG", "btn_list": "Danh sách", "btn_grid": "Lưới", "btn_export": "Xuất", @@ -21,7 +21,7 @@ "opt_preset_White_button_(iPad)": "Nút trắng (iPad)", "opt_preset_neon_dark": "🌑 Neon Tối", "opt_preset_gold": "👑 Gold", - "lbl_quick_palette": "🎨 Bảng màu nhanh", + "lbl_quick_palette": "🎨 Bảng màu danh sách", "swatch_bg_main": "Màu nền chính", "swatch_bg_main_short": "Nền", "swatch_text_color": "Màu chữ hệ thống", @@ -33,15 +33,15 @@ "swatch_list_svg": "SVG danh sách", "swatch_list_svg_short": "SVG DS", "lbl_title_adjust": "Sửa tiêu đề", - "lbl_title_size": "🔠 Cỡ chữ chính", - "lbl_title_spacing": "📐 Dãn chữ chính", - "lbl_icon_size": "🔠 Cỡ chữ Icon", - "lbl_icon_spacing": "📐 Dãn chữ Icon", + "lbl_title_size": "🔠 Tên", + "lbl_title_spacing": "📐 Khoảng cách", + "lbl_icon_size": "🔠 iCon", + "lbl_icon_spacing": "📐 Khoảng cách", "lbl_hide_labels": "Ẩn tên", - "lbl_list_frame": "Theme cho Danh sách", - "lbl_list_bg_opacity": "Đậm nhạt màu nút", + "lbl_list_frame": "Chủ đề danh sách", + "lbl_list_bg_opacity": "Đậm nhạt", "lbl_audio_feedback": "🎚️ Âm thanh", - "lbl_parallax": "🧭 Gia tốc kế 3D", + "lbl_parallax": "🧭 Hiệu ứng 3D", "lbl_bg_image": "📱 Hình nền tự chọn", "lbl_glassmorphism": "Bật Kính mờ", "info_title": "Quick Control", @@ -91,14 +91,14 @@ "lbl_layout_mode": "💠 Danh sách/Lưới", "msg_copy_success": "📄 Đã sao chép mã cấu hình!", "msg_prompt_import": "📥 Dán mã cấu hình vào đây:", - "lbl_media_widget": "🎵 Trình Phát Nhạc", + "lbl_media_widget": "🎶 Trình Phát Nhạc", "lbl_media_width": "📐 Kích thước khung", "lbl_media_bg_opacity": "Độ mờ khung", "lbl_media_btn_size": "📐 Cỡ nút bấm", "lbl_media_bg_color": "🏳️‍🌈 Màu khung", "lbl_media_btn_color": "🏳️‍🌈 Màu nút", "lbl_media_svg_color": "🏳️‍🌈 Màu SVG", - "lbl_media_theme_sync": "Đồng bộ Theme hệ thống", + "lbl_media_theme_sync": "Đồng bộ chủ đề hệ thống", "lbl_theme_dot": "💠 Chủ đề núm kéo", "lbl_thumb_size": "📐 Kích thước núm", "lbl_thumb_color": "🏳️‍🌈 Màu núm kéo", From 7452e496d1b8586eb155ea4dda4009c39421728d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 12:26:56 +0700 Subject: [PATCH 16/23] Update vi-VN.json --- Language/vi-VN.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Language/vi-VN.json b/Language/vi-VN.json index 9bfdafb..58343c6 100644 --- a/Language/vi-VN.json +++ b/Language/vi-VN.json @@ -1,7 +1,7 @@ { "app_title": "Quick Control", "settings_title": "CÀI ĐẶT", - "tab_appearance": "Giao diện", + "tab_appearance": "GIAO DIỆN", "tab_title": "TIÊU ĐỀ", "tab_frame": "KHUNG", "tab_media": "NHẠC", @@ -43,6 +43,9 @@ "lbl_audio_feedback": "🎚️ Âm thanh", "lbl_parallax": "🧭 Hiệu ứng 3D", "lbl_bg_image": "📱 Hình nền tự chọn", + "btn_choose_file": "Chọn tệp", + "file_none_selected": "chưa chọn tệp nào", + "file_selected_prefix": "Đã chọn: ", "lbl_glassmorphism": "Bật Kính mờ", "info_title": "Quick Control", "info_dev_prefix": "Phát triển bởi", From c7f0ffb79a14da6e8b89088d73e91fcc23a961f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 12:30:03 +0700 Subject: [PATCH 17/23] Update index.html --- index.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 9f13cd7..73d46fc 100644 --- a/index.html +++ b/index.html @@ -146,8 +146,14 @@
- - + + + +
From df1f196a75e30cfe6d33e787e08fa714e2c86d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 14:33:06 +0700 Subject: [PATCH 18/23] Update theme.js --- js/theme.js | 71 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/js/theme.js b/js/theme.js index 9420491..564917d 100644 --- a/js/theme.js +++ b/js/theme.js @@ -25,11 +25,11 @@ document.addEventListener("DOMContentLoaded", () => { if (mainContainer) mainContainer.classList.add('intro-zoom'); window.addEventListener('load', () => { - setTimeout(() => { - if (window.__dismissIntro) { window.__dismissIntro(); return; } + setTimeout(() =>tv { + if (window.__dismissIntro) {_se window.__dismissIntro(); return;en } if (introScreen) { - introScreen.classList.add('dismiss'); - if (mainContainer) mainContainer.classList.remove('intro-zoom'); +_p introScreen.classList.add('dismissreset'); + if (mainContainer) mainContainer.classList_b.remove('intro-zoom'); setTimeout(() => { introScreen.style.display = 'none'; }, 500); } }, 2000); @@ -40,7 +40,7 @@ document.addEventListener("DOMContentLoaded", () => { const badgePreset = document.getElementById('badge-preset'); const badgeTheme = document.getElementById('badge-theme'); - if (localStorage.getItem('sttv_seen_preset_badge') === 'true' && badgePreset) badgePreset.classList.add('hidden'); + if (localStorage.getItem('stadge') === 'true' && badgePreset) badgePreset.classList.add('hidden'); if (localStorage.getItem('sttv_seen_theme_badge') === 'true' && badgeTheme) badgeTheme.classList.add('hidden'); /* ========== TAB SWITCHING ========== */ @@ -265,12 +265,41 @@ document.addEventListener("DOMContentLoaded", () => { if (e.target.tagName === 'BUTTON' || e.target.type === 'checkbox' || e.target.closest('.theme-chip') || e.target.closest('.theme-chip-more')) playTick(); }); + /* ========== ⭐ FIX: COLOR PICKER KHÔNG BỊ VĂNG ========== */ + let colorPickerLock = false; + if (drawerEl) { - drawerEl.addEventListener('input', (e) => { if (e.target.type === 'color') updateLiveVariables(true); }); + // Khi user tap vào ô màu → khóa không cho re-render + drawerEl.addEventListener('focusin', (e) => { + if (e.target && e.target.type === 'color') { + colorPickerLock = true; + } + }); + + // Khi user đóng picker → mở khóa sau 400ms + drawerEl.addEventListener('focusout', (e) => { + if (e.target && e.target.type === 'color') { + setTimeout(() => { colorPickerLock = false; }, 400); + } + }); + + // CHỈ nhận màu khi user BUÔNG TAY (change event) drawerEl.addEventListener('change', (e) => { - if (e.target.type === 'color') { e.target.blur(); updateLiveVariables(true); } - else if (e.target.type === 'checkbox') updateLiveVariables(true); + if (e.target.type === 'color') { + updateLiveVariables(true); + setTimeout(() => { colorPickerLock = false; }, 150); + } + else if (e.target.type === 'checkbox') { + updateLiveVariables(true); + } }); + + // Block hoàn toàn event 'input' cho color picker để tránh văng + drawerEl.addEventListener('input', (e) => { + if (e.target && e.target.type === 'color') { + e.stopPropagation(); + } + }, true); } document.querySelectorAll('.shadow-switch').forEach(switchBtn => { @@ -314,11 +343,29 @@ document.addEventListener("DOMContentLoaded", () => { }); } + /* ========== UPLOAD BACKGROUND ========== */ const uploadBg = document.getElementById('upload-bg'); + const fileDisplay = document.getElementById('file-name-display'); + + function updateFileNameDisplay(fileName) { + if (!fileDisplay) return; + const prefix = (window.i18nData && window.i18nData['file_selected_prefix']) || 'Đã chọn: '; + const emptyText = (window.i18nData && window.i18nData['file_none_selected']) || 'chưa chọn tệp nào'; + if (fileName) { + fileDisplay.textContent = prefix + fileName; + fileDisplay.classList.add('has-file'); + } else { + fileDisplay.textContent = emptyText; + fileDisplay.classList.remove('has-file'); + } + } + window.__updateFileNameDisplay = updateFileNameDisplay; + if (uploadBg) { uploadBg.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { + updateFileNameDisplay(file.name); const reader = new FileReader(); reader.onload = (ev) => { const b64 = ev.target.result; @@ -329,12 +376,14 @@ document.addEventListener("DOMContentLoaded", () => { } }); } + const clearBgBtn = document.getElementById('clear-bg'); if (clearBgBtn) { clearBgBtn.addEventListener('click', () => { localStorage.removeItem('sttv_customBgImage'); root.style.setProperty('--bg-image', 'none'); if (uploadBg) uploadBg.value = ""; + updateFileNameDisplay(null); }); } @@ -355,7 +404,8 @@ document.addEventListener("DOMContentLoaded", () => { let adjustTimeout; if (drawerEl) { drawerEl.addEventListener('input', (e) => { - if (e.target.tagName === 'INPUT' && e.target.type !== 'color') { + // Chỉ áp dụng cho range, KHÔNG cho color + if (e.target.tagName === 'INPUT' && e.target.type === 'range') { drawerEl.classList.add('adjusting'); clearTimeout(adjustTimeout); adjustTimeout = setTimeout(() => drawerEl.classList.remove('adjusting'), 800); @@ -371,12 +421,14 @@ document.addEventListener("DOMContentLoaded", () => { function hexToRgba(hex, alpha) { let r = 0, g = 0, b = 0; + if (!hex) return `rgba(0,0,0,${alpha / 100})`; if (hex.length === 4) { r = parseInt(hex[1] + hex[1], 16); g = parseInt(hex[2] + hex[2], 16); b = parseInt(hex[3] + hex[3], 16); } else if (hex.length === 7) { r = parseInt(hex.substring(1, 3), 16); g = parseInt(hex.substring(3, 5), 16); b = parseInt(hex.substring(5, 7), 16); } return `rgba(${r}, ${g}, ${b}, ${alpha / 100})`; } function hexToRgb(hex) { let r = 0, g = 0, b = 0; + if (!hex) return '0,0,0'; if (hex.length === 4) { r = parseInt(hex[1] + hex[1], 16); g = parseInt(hex[2] + hex[2], 16); b = parseInt(hex[3] + hex[3], 16); } else if (hex.length === 7) { r = parseInt(hex.substring(1, 3), 16); g = parseInt(hex.substring(3, 5), 16); b = parseInt(hex.substring(5, 7), 16); } return `${r}, ${g}, ${b}`; @@ -501,6 +553,7 @@ document.addEventListener("DOMContentLoaded", () => { localStorage.removeItem('sttv_customBgImage'); root.style.setProperty('--bg-image', 'none'); if (uploadBg) uploadBg.value = ""; + if (window.__updateFileNameDisplay) window.__updateFileNameDisplay(null); const decoded = decodeURIComponent(code.trim()); const data = decoded.split('|'); From feb037860056f55d7ffb8033d02ddef39abbe5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 14:34:32 +0700 Subject: [PATCH 19/23] Update index.html --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 73d46fc..5afb8ef 100644 --- a/index.html +++ b/index.html @@ -346,7 +346,7 @@

Quick Control

})(); - + \ No newline at end of file From dae1f90bcb8f9c5baa3aea3d9c13bdcc5f08a1c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 14:47:59 +0700 Subject: [PATCH 20/23] Update index.html --- index.html | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/index.html b/index.html index 5afb8ef..22aa4f0 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,7 @@ Trung tâm điều khiển - Sentechtipvn - + @@ -38,7 +38,7 @@ -
-
- +
-
@@ -90,7 +88,7 @@
- +
@@ -117,7 +115,6 @@
-
@@ -146,14 +143,14 @@
- + - - + +
@@ -166,7 +163,8 @@
-
+ +
Đậm nhạt màu nút
@@ -185,7 +183,6 @@
-
@@ -198,14 +195,14 @@
-
-
+ +
Khung
Bo góc
@@ -216,9 +213,9 @@
-
-
+ +
Kích thước khung
Độ mờ khung
@@ -242,7 +239,7 @@
- +
@@ -347,6 +344,6 @@

Quick Control

- + \ No newline at end of file From bb80da5a8cead0b6a954a451c60b3216ba7b3ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 14:48:37 +0700 Subject: [PATCH 21/23] Update theme.js --- js/theme.js | 54 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/js/theme.js b/js/theme.js index 564917d..a3bf031 100644 --- a/js/theme.js +++ b/js/theme.js @@ -1,5 +1,9 @@ const DEV_MODE = true; +// ⭐ VERSION CONTROL CHO BADGE THÔNG BÁO +const PRESET_VERSION = 3; // Tăng số này mỗi khi thêm preset giao diện mới +const THEME_VERSION = 2; // Tăng số này mỗi khi thêm Theme SVG mới + const SHADOW_MODES = [ { id: 'inset', nameKey: 'shadow_inset', name: 'Bóng Chìm', template: 'inset {x}px {y}px {b}px {s}px {c}' }, { id: 'outer', nameKey: 'shadow_outer', name: 'Bóng Ngoài', template: '{x}px {y}px {b}px {s}px {c}' }, @@ -25,11 +29,11 @@ document.addEventListener("DOMContentLoaded", () => { if (mainContainer) mainContainer.classList.add('intro-zoom'); window.addEventListener('load', () => { - setTimeout(() =>tv { - if (window.__dismissIntro) {_se window.__dismissIntro(); return;en } + setTimeout(() => { + if (window.__dismissIntro) { window.__dismissIntro(); return; } if (introScreen) { -_p introScreen.classList.add('dismissreset'); - if (mainContainer) mainContainer.classList_b.remove('intro-zoom'); + introScreen.classList.add('dismiss'); + if (mainContainer) mainContainer.classList.remove('intro-zoom'); setTimeout(() => { introScreen.style.display = 'none'; }, 500); } }, 2000); @@ -37,11 +41,29 @@ _p introScreen.classList.add('dismissreset'); if (!DEV_MODE) document.body.classList.add('standard-mode'); + /* ========== ⭐ BADGE VERSION-BASED ========== */ const badgePreset = document.getElementById('badge-preset'); const badgeTheme = document.getElementById('badge-theme'); - if (localStorage.getItem('stadge') === 'true' && badgePreset) badgePreset.classList.add('hidden'); - if (localStorage.getItem('sttv_seen_theme_badge') === 'true' && badgeTheme) badgeTheme.classList.add('hidden'); + // Preset badge: hiện khi bạn tăng PRESET_VERSION + const seenPresetVersion = parseInt(localStorage.getItem('sttv_presetVersionSeen') || '0'); + if (badgePreset) { + if (seenPresetVersion >= PRESET_VERSION) { + badgePreset.classList.add('hidden'); + } else { + badgePreset.classList.remove('hidden'); + } + } + + // Theme badge: hiện khi bạn tăng THEME_VERSION + const seenThemeVersion = parseInt(localStorage.getItem('sttv_themeVersionSeen') || '0'); + if (badgeTheme) { + if (seenThemeVersion >= THEME_VERSION) { + badgeTheme.classList.add('hidden'); + } else { + badgeTheme.classList.remove('hidden'); + } + } /* ========== TAB SWITCHING ========== */ const tabButtons = document.querySelectorAll('.settings-tab'); @@ -179,7 +201,11 @@ _p introScreen.classList.add('dismissreset'); if (currentThemeTarget === 'frame') { const vtf = document.getElementById('val-theme-frame'); if (vtf) vtf.value = val; - if (badgeTheme) { badgeTheme.classList.add('hidden'); localStorage.setItem('sttv_seen_theme_badge', 'true'); } + // ⭐ Đánh dấu đã xem Theme version mới + if (badgeTheme) { + badgeTheme.classList.add('hidden'); + localStorage.setItem('sttv_themeVersionSeen', String(THEME_VERSION)); + } resetPresetToCustom(); } else if (currentThemeTarget === 'thumb') { const vtt = document.getElementById('val-thumb-theme'); @@ -215,12 +241,17 @@ _p introScreen.classList.add('dismissreset'); }; } + /* ========== PRESET SELECT ========== */ const presetSelect = document.getElementById('val-theme-preset'); if (presetSelect) { presetSelect.addEventListener('change', (e) => { const val = e.target.value; localStorage.setItem('sttv_activePreset', val); - if (badgePreset) { badgePreset.classList.add('hidden'); localStorage.setItem('sttv_seen_preset_badge', 'true'); } + // ⭐ Đánh dấu đã xem Preset version mới + if (badgePreset) { + badgePreset.classList.add('hidden'); + localStorage.setItem('sttv_presetVersionSeen', String(PRESET_VERSION)); + } if (val !== 'none') unpackConfig(val); }); } @@ -265,25 +296,22 @@ _p introScreen.classList.add('dismissreset'); if (e.target.tagName === 'BUTTON' || e.target.type === 'checkbox' || e.target.closest('.theme-chip') || e.target.closest('.theme-chip-more')) playTick(); }); - /* ========== ⭐ FIX: COLOR PICKER KHÔNG BỊ VĂNG ========== */ + /* ========== COLOR PICKER KHÔNG BỊ VĂNG ========== */ let colorPickerLock = false; if (drawerEl) { - // Khi user tap vào ô màu → khóa không cho re-render drawerEl.addEventListener('focusin', (e) => { if (e.target && e.target.type === 'color') { colorPickerLock = true; } }); - // Khi user đóng picker → mở khóa sau 400ms drawerEl.addEventListener('focusout', (e) => { if (e.target && e.target.type === 'color') { setTimeout(() => { colorPickerLock = false; }, 400); } }); - // CHỈ nhận màu khi user BUÔNG TAY (change event) drawerEl.addEventListener('change', (e) => { if (e.target.type === 'color') { updateLiveVariables(true); @@ -294,7 +322,6 @@ _p introScreen.classList.add('dismissreset'); } }); - // Block hoàn toàn event 'input' cho color picker để tránh văng drawerEl.addEventListener('input', (e) => { if (e.target && e.target.type === 'color') { e.stopPropagation(); @@ -404,7 +431,6 @@ _p introScreen.classList.add('dismissreset'); let adjustTimeout; if (drawerEl) { drawerEl.addEventListener('input', (e) => { - // Chỉ áp dụng cho range, KHÔNG cho color if (e.target.tagName === 'INPUT' && e.target.type === 'range') { drawerEl.classList.add('adjusting'); clearTimeout(adjustTimeout); From eb8ae52d733088d88fd2e082b363d594dc3dc1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:02:31 +0700 Subject: [PATCH 22/23] Update index.html --- index.html | 54 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/index.html b/index.html index 22aa4f0..6dc3924 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,7 @@ Trung tâm điều khiển - Sentechtipvn - + @@ -33,26 +33,27 @@
Quick Control
+
- - - + + + + + + + + +
- +
- +
@@ -88,7 +89,7 @@
- +
@@ -163,7 +164,6 @@
-
Đậm nhạt màu nút
@@ -201,7 +201,6 @@
-
Khung
@@ -214,7 +213,6 @@
-
Kích thước khung
@@ -239,7 +237,6 @@
-
@@ -341,9 +338,28 @@

Quick Control

setTimeout(dismissIntro, 3500); window.__dismissIntro = dismissIntro; })(); + + // ⭐ FIX: Gắn sự kiện cho 2 thanh trượt vol/bri bằng JS (bỏ inline onchange) + document.addEventListener('DOMContentLoaded', function() { + var volumeSlider = document.getElementById('sys-volume'); + var brightnessSlider = document.getElementById('sys-brightness'); + + if (volumeSlider) { + volumeSlider.addEventListener('change', function() { + var prefix = this.dataset.shortcutPrefix || 'vol_'; + window.location.href = 'shortcuts://run-shortcut?name=QuickControl&input=text&text=' + prefix + this.value; + }); + } + if (brightnessSlider) { + brightnessSlider.addEventListener('change', function() { + var prefix = this.dataset.shortcutPrefix || 'bri_'; + window.location.href = 'shortcuts://run-shortcut?name=QuickControl&input=text&text=' + prefix + this.value; + }); + } + }); - - + + \ No newline at end of file From 8dcd855da59a3cb2cdcd213feb72404fcc691e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Ng=E1=BB=8Dc=20Sen?= <70382284+Sentechtipsvn@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:05:47 +0700 Subject: [PATCH 23/23] Update style.css --- CSS/style.css | 166 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 151 insertions(+), 15 deletions(-) diff --git a/CSS/style.css b/CSS/style.css index c053202..8481e24 100644 --- a/CSS/style.css +++ b/CSS/style.css @@ -125,14 +125,81 @@ body.standard-mode .dev-only { display: none !important; } .app-container.list-mode.list-frame-active .icon-box svg { fill: var(--svg-color); color: var(--svg-color); width: var(--svg-size); height: var(--svg-size); opacity: var(--svg-opacity); } /* ============================================ - MEDIA WIDGET + MEDIA WIDGET - FIX DISPLAY ============================================ */ -.media-player-widget { margin: 10px auto 30px auto; width: var(--media-width); max-width: 400px; padding: 20px; border-radius: 24px; display: flex; flex-direction: column; gap: 22px; background: rgba(var(--media-bg-rgb), var(--media-bg-opacity)); box-shadow: var(--btn-shadow); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); } -.media-controls { display: flex; align-items: center; justify-content: center; gap: 30px; } -.media-btn { border: none; background: var(--media-btn-color); color: var(--media-svg-color); border-radius: 50%; width: var(--media-btn-size); height: var(--media-btn-size); display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: var(--btn-shadow); transition: all 0.15s ease; } -.media-btn.play-main { width: var(--media-btn-play); height: var(--media-btn-play); } -.media-btn:active { box-shadow: inset 0 0 10px rgba(0,0,0,0.3); transform: scale(0.95); } -.media-player-widget.theme-synced .media-btn { background-image: var(--frame-bg) !important; background-color: var(--frame-bg-color) !important; background-size: cover; background-position: center; box-shadow: var(--btn-shadow) !important; } +.media-player-widget { + margin: 10px auto 30px auto; + width: var(--media-width); + max-width: 400px; + padding: 20px; + border-radius: 24px; + display: flex; + flex-direction: column; + gap: 22px; + background: rgba(var(--media-bg-rgb), var(--media-bg-opacity)); + box-shadow: var(--btn-shadow); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid rgba(255,255,255,0.1); +} + +.media-controls { + display: flex; + align-items: center; + justify-content: center; + gap: 30px; +} + +/* ⭐ FIX: Media buttons dùng thẻ thay vì