// =========== Custom Select Dropdowns =========== function ufSelectToggle(id) { const wrap = document.getElementById(id + '-wrap') || document.getElementById(id + '-trigger')?.closest('.uf-select-wrap'); const trigger = document.getElementById(id + '-trigger'); const dropdown = document.getElementById(id + '-dropdown'); const arrow = document.getElementById(id + '-arrow'); if (!dropdown) return; const isOpen = !dropdown.classList.contains('hidden'); // Close all open dropdowns first document.querySelectorAll('.uf-select-dropdown').forEach(d => { d.classList.add('hidden'); d.classList.remove('uf-select-dropdown-up'); }); document.querySelectorAll('.uf-select-arrow').forEach(a => a.classList.remove('uf-select-arrow-open')); if (!isOpen) { // Detect direction: open upward only if there's not enough space below if (trigger) { const rect = trigger.getBoundingClientRect(); const spaceBelow = window.innerHeight - rect.bottom; const spaceAbove = rect.top; const dropH = 240; // max-height of dropdown if (spaceBelow < dropH && spaceAbove > spaceBelow) { dropdown.classList.add('uf-select-dropdown-up'); } } dropdown.classList.remove('hidden'); if (arrow) arrow.classList.add('uf-select-arrow-open'); } } function ufSelectPick(id, value, label) { const hidden = document.getElementById(id); const labelEl = document.getElementById(id + '-label'); const dropdown = document.getElementById(id + '-dropdown'); const arrow = document.getElementById(id + '-arrow'); if (hidden) hidden.value = value; if (labelEl) { labelEl.textContent = label; labelEl.style.color = '#ffffff'; // selected = white, placeholder was grey } if (dropdown) dropdown.classList.add('hidden'); if (arrow) arrow.classList.remove('uf-select-arrow-open'); // Mark active option if (dropdown) { dropdown.querySelectorAll('.uf-select-option').forEach(opt => { opt.classList.toggle('uf-select-option-active', opt.dataset.value === value); }); } // Fire side-effects by id if (id === 'live-palette') applyPalette(value); } // Close on outside click document.addEventListener('click', function(e) { if (!e.target.closest('.uf-select-wrap')) { document.querySelectorAll('.uf-select-dropdown').forEach(d => d.classList.add('hidden')); document.querySelectorAll('.uf-select-arrow').forEach(a => a.classList.remove('uf-select-arrow-open')); } }); // =========== Rolling Counter =========== function animateValue(obj, start, end, duration) { let startTimestamp = null; const isPct = typeof end === 'string' && end.includes('%'); const endNum = isPct ? parseFloat(end) : end; const startNum = parseFloat(start) || 0; const step = (timestamp) => { if (!startTimestamp) startTimestamp = timestamp; const progress = Math.min((timestamp - startTimestamp) / duration, 1); const ease = progress * (2 - progress); const current = ease * (endNum - startNum) + startNum; if (isPct) { obj.innerText = current.toFixed(1) + '%'; } else { obj.innerText = Math.floor(current); } if (progress < 1) { window.requestAnimationFrame(step); } else { obj.innerText = end; } }; window.requestAnimationFrame(step); } // Close all open tooltips function closeAllTips() { document.querySelectorAll('.info-tip').forEach(tip => { tip.style.display = 'none'; }); } function positionTip(tip, wrap) { const rect = wrap.getBoundingClientRect(); const tipH = tip.offsetHeight || 60; if (rect.bottom + tipH + 10 > window.innerHeight) { tip.style.top = (rect.top - tipH - 6) + 'px'; } else { tip.style.top = (rect.bottom + 6) + 'px'; } tip.style.left = Math.min(rect.left, window.innerWidth - 260) + 'px'; } // Desktop hover only — never fires on touch document.addEventListener('mouseover', e => { if (window.matchMedia('(hover: none)').matches) return; const wrap = e.target.closest('.info-wrap'); if (!wrap) return; const tip = wrap.querySelector('.info-tip'); if (!tip) return; tip.style.display = 'block'; positionTip(tip, wrap); }); document.addEventListener('mouseout', e => { if (window.matchMedia('(hover: none)').matches) return; const wrap = e.target.closest('.info-wrap'); if (!wrap) return; const tip = wrap.querySelector('.info-tip'); if (tip) tip.style.display = 'none'; }); // Tap/click toggle — works on both desktop and mobile document.addEventListener('click', e => { const btn = e.target.closest('.info-btn'); if (!btn) { if (!e.target.closest('.info-tip') && !e.target.closest('.info-wrap')) { closeAllTips(); } return; } e.stopPropagation(); const wrap = btn.closest('.info-wrap'); if (!wrap) return; const tip = wrap.querySelector('.info-tip'); if (!tip) return; const isVisible = tip.style.display === 'block'; closeAllTips(); if (!isVisible) { tip.style.display = 'block'; positionTip(tip, wrap); } }); // ---- Tab switching — updates both sidebar + mobile bottom nav ---- function switchTab(tab) { const allTabs = ['about', 'overview', 'results', 'settings', 'help', 'feedback', 'profile']; allTabs.forEach(t => { const el = document.getElementById('tab-' + t); if (el) el.classList.toggle('hidden', tab !== t); const nav = document.getElementById('nav-' + t); if (nav) { if (tab === t) { nav.classList.add('nav-item-active'); nav.classList.remove('nav-item-inactive'); } else { nav.classList.remove('nav-item-active'); nav.classList.add('nav-item-inactive'); } } const mobNav = document.getElementById('mob-nav-' + t); if (mobNav) mobNav.classList.toggle('active', tab === t); }); // Refresh Profile UI every switch if (typeof populateProfileUI === 'function') populateProfileUI(); if (tab === 'profile') populateProfileTab(); // Stop glow notification when user views Results if (tab === 'results') { const mobResults = document.getElementById('mob-nav-results'); if (mobResults) mobResults.classList.remove('notify-glow'); const navResults = document.getElementById('nav-results'); if (navResults) navResults.classList.remove('notify-glow'); } // Store active tab sessionStorage.setItem('uf_active_tab', tab); } // Ensure global access window.switchTab = switchTab; // =========== Toast System =========== function showToast(message, type) { type = type || 'info'; const icons = { success: 'fa-check-circle', error: 'fa-circle-xmark', info: 'fa-circle-info' }; const el = document.createElement('div'); el.className = `toast toast-${type}`; el.innerHTML = ` ${message}`; document.getElementById('toast-container').appendChild(el); setTimeout(() => { el.classList.add('toast-out'); setTimeout(() => el.remove(), 300); }, 3000); } // =========== Keyboard Shortcuts =========== const TAB_KEYS = { '1': 'about', '2': 'overview', '3': 'results', '4': 'settings', '5': 'help', '6': 'feedback', '7': 'profile' }; function downloadArtifacts() { const vid = document.body.dataset.lastVideoId; if (vid) { const btn = document.getElementById('btn-download-bundle'); if (btn) { const originalHTML = btn.innerHTML; btn.innerHTML = ` Zipping...`; btn.disabled = true; setTimeout(() => { btn.innerHTML = originalHTML; btn.disabled = false; }, 4000); } window.open(`bundle/${vid}`, '_blank'); showToast('Preparing download bundle...', 'info'); } } document.addEventListener('keydown', function(e) { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return; if (TAB_KEYS[e.key]) { switchTab(TAB_KEYS[e.key]); return; } if (e.key === 'd' || e.key === 'D') { downloadArtifacts(); } }); // =========== Feedback =========== let _fbRating = 0; let _fbEmojis = { 'fb-recommend': null, 'fb-security': null, 'fb-integration': null, 'fb-ease': null }; function setRating(n) { _fbRating = n; document.querySelectorAll('.fb-star').forEach(s => { s.classList.toggle('active', parseInt(s.dataset.v) <= n); }); } function setEmoji(el, qId, val) { _fbEmojis[qId] = val; const container = document.getElementById(qId); if (container) { container.querySelectorAll('.fb-emoji-btn').forEach(btn => btn.classList.remove('active')); } el.classList.add('active'); } // These are uf-select widgets (hidden input + label), not