diff --git a/CSS/style.css b/CSS/style.css index ebe0dcc..8481e24 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,61 @@ 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: 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); } .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 +95,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,102 +107,288 @@ 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-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 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; +} + +/* ⭐ FIX: Media buttons dùng thẻ thay vì - - + + + + + + + + +
- +
- +
- + +
-
+
-

⚙️ Cài đặt

- + +
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 - +
+
+ +
💫 Cỡ chữ chính
+
📐 Dãn chữ chính
+
+
💫 Cỡ chữ Icon
+
📐 Dãn chữ Icon
+
-
- -
- -
-
-
Black
-
Red
-
Green
-
Orange
- -
Purple
-
Moss
-
Glass
-
Convex glass
-
+
+ +
+
+ +
- -
- -
- -
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
-
Màu khung
-
Màu nút
-
Màu SVG
+
+
-

🔘 Đổ Bóng

-
-
@@ -209,7 +250,7 @@

🔘 Đổ Bó
-
Kho Giao Diện
+
Kho Giao Diện

@@ -235,48 +276,90 @@

🔘 Đổ 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 diff --git a/js/theme.js b/js/theme.js index f394d17..a3bf031 100644 --- a/js/theme.js +++ b/js/theme.js @@ -1,880 +1,901 @@ 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}' }, - { 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'); - 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); + } - if (mainContainer) 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); - }); - - if (!DEV_MODE) document.body.classList.add('standard-mode'); - - 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%'; + if (mainContainer) mainContainer.classList.add('intro-zoom'); - 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)`; - }; - - 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')); - }); - - if (shadowContainer) { - SHADOW_MODES.forEach(mode => { - const div = document.createElement('div'); - div.className = 'shadow-item dev-only'; - div.innerHTML = ` -
- ${mode.name} - -
-
-
-
-
-
-
-
-
- `; - shadowContainer.appendChild(div); - }); - } - - 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.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(); - }); - }); - - function syncThemePickerVisuals(val) { - document.querySelectorAll('.theme-chip').forEach(c => { - if (c.dataset.value === val) c.classList.add('active'); - else c.classList.remove('active'); + 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); }); - } - 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 (!DEV_MODE) document.body.classList.add('standard-mode'); - 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 (themeOverlay && themeModal) { - themeOverlay.onclick = () => { themeModal.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; - safeSetItem('sttv_activePreset', val); - if (badgePreset) { - badgePreset.classList.add('hidden'); - safeSetItem('sttv_seen_preset_badge', 'true'); + /* ========== ⭐ BADGE VERSION-BASED ========== */ + const badgePreset = document.getElementById('badge-preset'); + const badgeTheme = document.getElementById('badge-theme'); + + // 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'); } - if (val !== 'none') unpackConfig(val); - }); - } - - function resetPresetToCustom() { - if (presetSelect) { - presetSelect.value = 'none'; - safeSetItem('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); - }); - } - const AudioContext = window.AudioContext || window.webkitAudioContext; - let audioCtx; - 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 (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(); + /* ========== 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) {} + }); }); - } - 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'); + 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'); } - resetPresetToCustom(); - updateLiveVariables(); - }); - }); - - function handleOrientation(e) { - const toggleParallax = document.getElementById('toggle-parallax'); - if (!toggleParallax || !toggleParallax.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'); - } + } - 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); + /* ========== 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(); } - } else { - window.removeEventListener('deviceorientation', handleOrientation); - root.style.setProperty('--tilt-x', '0px'); - root.style.setProperty('--tilt-y', '0px'); + updateLiveVariables(false); } - safeSetItem('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 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(); } }); - } - 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 = ""; - }); - } - 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; - - 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(); - }); - } + /* ========== 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); + }); + } - 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(); }; + 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'); + }); + } - const infoModal = document.getElementById('info-modal'); - const infoOverlay = document.getElementById('info-overlay'); - const btnInfo = document.getElementById('btn-info'); - const closeInfoBtn = document.getElementById('close-info'); + 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'); + }; + } - if (btnInfo && infoModal && infoOverlay) { - btnInfo.onclick = () => { + 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'); + }; + } + + 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; + // ⭐ Đá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'); + if (vtt) vtt.value = val; + } + updateLiveVariables(true); + }); + }); + + const closeThemeModal = document.getElementById('close-theme-modal'); + if (closeThemeModal && themeModal) closeThemeModal.onclick = () => { + themeModal.classList.remove('show'); + if (themeOverlay) 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'); - infoOverlay.classList.add('show'); + if (themeOverlay) themeOverlay.classList.add('show'); }; - } - if (closeInfoBtn && infoModal && infoOverlay) { - closeInfoBtn.onclick = () => { + const closeInfoModal = document.getElementById('close-info-modal'); + if (closeInfoModal && infoModal) closeInfoModal.onclick = () => { infoModal.classList.remove('show'); - infoOverlay.classList.remove('show'); + if (themeOverlay) themeOverlay.classList.remove('show'); }; - } - if (infoOverlay && infoModal) { - infoOverlay.onclick = (e) => { - if (e.target.id === 'info-overlay') { - infoModal.classList.remove('show'); - infoOverlay.classList.remove('show'); + + if (themeOverlay) { + themeOverlay.onclick = () => { + if (themeModal) themeModal.classList.remove('show'); + if (infoModal) infoModal.classList.remove('show'); + themeOverlay.classList.remove('show'); + }; + } + + /* ========== 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); + // ⭐ Đá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); + }); + } + + function resetPresetToCustom() { + if (presetSelect) { + presetSelect.value = 'none'; + localStorage.setItem('sttv_activePreset', 'none'); } - }; - } + } - 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'; + 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); + }); } - configModal.classList.add('show'); - configOverlay.classList.add('show'); - setTimeout(() => { - try { - if (mode === 'export') configTextarea.select(); - else configTextarea.focus(); + + 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) {} - }, 250); - } - function closeConfigModal() { - if (configModal) configModal.classList.remove('show'); - if (configOverlay) configOverlay.classList.remove('show'); - } + } + 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 (btnConfigClose) btnConfigClose.onclick = closeConfigModal; - if (configOverlay) configOverlay.onclick = closeConfigModal; + /* ========== COLOR PICKER KHÔNG BỊ VĂNG ========== */ + let colorPickerLock = false; - 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 (drawerEl) { + drawerEl.addEventListener('focusin', (e) => { + if (e.target && e.target.type === 'color') { + colorPickerLock = true; } - }; + }); - 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(); - }; - } + drawerEl.addEventListener('focusout', (e) => { + if (e.target && e.target.type === 'color') { + setTimeout(() => { colorPickerLock = false; }, 400); + } + }); + + drawerEl.addEventListener('change', (e) => { + if (e.target.type === 'color') { + updateLiveVariables(true); + setTimeout(() => { colorPickerLock = false; }, 150); + } + else if (e.target.type === 'checkbox') { + updateLiveVariables(true); + } + }); - 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}`; - } + drawerEl.addEventListener('input', (e) => { + if (e.target && e.target.type === 'color') { + e.stopPropagation(); + } + }, true); + } - 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 hexColor = drawer.querySelector('.s-c').value; - const opacity = drawer.querySelector('.s-o').value; - const rgbaColor = hexToRgba(hexColor, opacity); - - 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; + 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); + }); }); - root.style.setProperty('--btn-shadow', combinedShadow || 'none'); - } - function saveSettingsToLocal() { - const get = id => document.getElementById(id); - - 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); + 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'); } - 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); + 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); + }); } - 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 - }; - } - }); - safeSetItem('sttv_shadowConfig', JSON.stringify(shadowState)); - } - - function packConfig() { - 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 - }; + /* ========== 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; + 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 = ""; + updateFileNameDisplay(null); + }); + } - 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 + 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 === 'range') { + drawerEl.classList.add('adjusting'); + clearTimeout(adjustTimeout); + adjustTimeout = setTimeout(() => drawerEl.classList.remove('adjusting'), 800); + } + }); + } - return btoa(unescape(encodeURIComponent(JSON.stringify(fullConfig)))); - } + 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) 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}`; + } - function unpackConfig(base64Str) { - 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) {} - 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]; - } + 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 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 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); } + } + + 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('*')); } - 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); + }); + + 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('|')); + } + + 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 = ""; + if (window.__updateFileNameDisplay) window.__updateFileNameDisplay(null); + + 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; } - 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 : ''); - } + 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 (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; + 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."); } - updateLiveVariables(); - } catch(e) { - console.error(e); - alert("Mã cấu hình không hợp lệ!"); } - } - const btnExport = document.getElementById('btn-export'); - if (btnExport) btnExport.onclick = () => openConfigModal('export'); + 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); + } + }); + } - const btnImport = document.getElementById('btn-import'); - if (btnImport) btnImport.onclick = () => openConfigModal('import'); + 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(); } + }); + } - 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; }; + 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(); + } + }); + } - 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); - - const rawRadius = g('val-frame-radius'); - if (rawRadius !== '') { + 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'); - } - 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 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'); } - } + 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'); + } + } - const isListFrame = c('toggle-list-frame'); - if (mainContainer) { - if (isListFrame) mainContainer.classList.add('list-frame-active'); - else mainContainer.classList.remove('list-frame-active'); - } + if (mainContainer) { + if (c('toggle-list-frame')) 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); - - 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'); - } - - const glassMode = c('toggle-glass'); - if (mainContainer) { - if (glassMode) mainContainer.classList.add('glass-active'); - else mainContainer.classList.remove('glass-active'); - } + 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'); + } - 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-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'); - } - updateShadow(); - saveSettingsToLocal(); - } + 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 = safeGetItem('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'); - - 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'); - 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'); - - const savedShadowConfig = safeGetItem('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; + 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; + }; + + 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) { 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 (btnLayoutList && btnLayoutGrid) { + if (initLayout === 'list') { + btnLayoutList.classList.add('active'); + btnLayoutGrid.classList.remove('active'); + } else { + btnLayoutGrid.classList.add('active'); + btnLayoutList.classList.remove('active'); + } + } - const savedBg = safeGetItem('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(); + loadSettingsFromLocal(); + updateLiveVariables(true); - document.addEventListener("visibilitychange", function() { - if (document.visibilityState === 'hidden') saveSettingsToLocal(); - }); - window.addEventListener("beforeunload", saveSettingsToLocal); - window.addEventListener("pagehide", 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