Git Komut Rehberi & Terminal Asistanı
Sıfırdan Yeni Bir Depo Başlatın:
Mevcut Bir Git Deposu veya Klasörü Gönderin:
Hataları Geri Alma, İptal & Temizlik:
Son commit'i geri al (yapılan değişiklikleri korur):
git reset --soft HEAD~1
Yapılan yerel değişiklikleri iptal et (çalışma ağacını temizler):
git restore .
Takip edilmeyen gereksiz dosyaları sil:
git clean -fd
Değişiklikleri geçici sakla ve geri getir (stash):
git stash git stash pop
Dal (Branch) ve Sürüm Etiketleri:
Yeni dal aç ve geçiş yap:
git checkout -b yeni-ozellik git push -u origin yeni-ozellik
Sürüm etiketi (tag) oluştur ve gönder:
git tag -a v1.0.0 -m "Sürüm 1.0.0" git push origin --tags
feat(v1.5.0): unified observer, sleep timer, double-tap seek, voice assistant commands & qr handoff
This commit is contained in:
@@ -31,7 +31,10 @@ if (typeof window !== 'undefined') {
|
|||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', neuterIncompatible);
|
document.addEventListener('DOMContentLoaded', neuterIncompatible);
|
||||||
}
|
}
|
||||||
setInterval(neuterIncompatible, 1000);
|
var neuterInterval = setInterval(neuterIncompatible, 1000);
|
||||||
|
setTimeout(function () {
|
||||||
|
clearInterval(neuterInterval);
|
||||||
|
}, 5000);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -951,14 +954,12 @@ try {
|
|||||||
} else if (type === 'checkbox') {
|
} else if (type === 'checkbox') {
|
||||||
inp.setAttribute('aria-label', 'Seçim Kutusu');
|
inp.setAttribute('aria-label', 'Seçim Kutusu');
|
||||||
} else if (type === 'password') {
|
} else if (type === 'password') {
|
||||||
inp.setAttribute('aria-label', 'Åifre');
|
inp.setAttribute('aria-label', 'Şifre');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(autoFixDOM, 1000);
|
|
||||||
|
|
||||||
// 3. Screen-Reader & Turkish Q Optimized Shortcuts & Player Bar (v1.4.1)
|
// 3. Screen-Reader & Turkish Q Optimized Shortcuts & Player Bar (v1.4.1)
|
||||||
function toggleShortcutsModal() {
|
function toggleShortcutsModal() {
|
||||||
var modal = document.getElementById('yaktube-shortcuts-dialog');
|
var modal = document.getElementById('yaktube-shortcuts-dialog');
|
||||||
@@ -997,6 +998,8 @@ try {
|
|||||||
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + T</span> veya <span class="yaktube-kbd">T</span></td><td>Sinema / Tiyatro Modu</td></tr>' +
|
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + T</span> veya <span class="yaktube-kbd">T</span></td><td>Sinema / Tiyatro Modu</td></tr>' +
|
||||||
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + I</span> veya <span class="yaktube-kbd">I</span></td><td>Hızlı Video Bilgisi & İstatistikler</td></tr>' +
|
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + I</span> veya <span class="yaktube-kbd">I</span></td><td>Hızlı Video Bilgisi & İstatistikler</td></tr>' +
|
||||||
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + C</span> veya <span class="yaktube-kbd">C</span></td><td>Altyazıları Aç / Kapat</td></tr>' +
|
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + C</span> veya <span class="yaktube-kbd">C</span></td><td>Altyazıları Aç / Kapat</td></tr>' +
|
||||||
|
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + U</span></td><td>⏱️ Uyku Zamanlayıcısı (Sleep Timer)</td></tr>' +
|
||||||
|
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + Q</span></td><td>📱 Telefonda Devam Et (QR Kod)</td></tr>' +
|
||||||
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + .</span> / <span class="yaktube-kbd">Alt + ,</span></td><td>Oynatma hızını artır / azalt</td></tr>' +
|
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + .</span> / <span class="yaktube-kbd">Alt + ,</span></td><td>Oynatma hızını artır / azalt</td></tr>' +
|
||||||
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + S</span> veya <span class="yaktube-kbd">/</span> (Shift+7)</td><td>Arama penceresini aç</td></tr>' +
|
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + S</span> veya <span class="yaktube-kbd">/</span> (Shift+7)</td><td>Arama penceresini aç</td></tr>' +
|
||||||
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + V</span> veya <span class="yaktube-kbd">Shift + V</span></td><td>Sesli aramayı başlat</td></tr>' +
|
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + V</span> veya <span class="yaktube-kbd">Shift + V</span></td><td>Sesli aramayı başlat</td></tr>' +
|
||||||
@@ -1045,6 +1048,7 @@ try {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
window.getVideoElement = getVideoElement;
|
||||||
|
|
||||||
function formatTime(seconds) {
|
function formatTime(seconds) {
|
||||||
if (isNaN(seconds) || seconds < 0) return '0 saniye';
|
if (isNaN(seconds) || seconds < 0) return '0 saniye';
|
||||||
@@ -1143,6 +1147,493 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// YAKTUBE QUICK TOAST HELPER (v1.5.0)
|
||||||
|
// ==========================================================================
|
||||||
|
function showQuickToast(message, icon) {
|
||||||
|
var existing = document.getElementById('yaktube-quick-toast');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
|
||||||
|
var toast = document.createElement('div');
|
||||||
|
toast.id = 'yaktube-quick-toast';
|
||||||
|
toast.className = 'yaktube-quick-toast';
|
||||||
|
toast.style.cssText =
|
||||||
|
'position:fixed!important;top:80px!important;left:50%!important;transform:translateX(-50%)!important;' +
|
||||||
|
'background:rgba(24,24,31,0.92)!important;border:1px solid #ff8f37!important;color:#fff!important;' +
|
||||||
|
'padding:10px 22px!important;border-radius:30px!important;font-size:14px!important;font-weight:700!important;' +
|
||||||
|
'box-shadow:0 10px 30px rgba(0,0,0,0.8)!important;z-index:999999!important;pointer-events:none!important;' +
|
||||||
|
'backdrop-filter:blur(8px)!important;display:flex!important;align-items:center!important;gap:8px!important;';
|
||||||
|
toast.innerHTML = (icon ? '<span>' + icon + '</span> ' : '') + '<span>' + message + '</span>';
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
if (toast && toast.parentNode) {
|
||||||
|
toast.style.opacity = '0';
|
||||||
|
toast.style.transition = 'opacity 0.3s ease-out';
|
||||||
|
setTimeout(function () {
|
||||||
|
if (toast.parentNode) toast.parentNode.removeChild(toast);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}, 2200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// YAKTUBE SLEEP TIMER ENGINE (v1.5.0)
|
||||||
|
// ==========================================================================
|
||||||
|
var sleepTimer = {
|
||||||
|
countdownInterval: null,
|
||||||
|
targetTime: 0,
|
||||||
|
mode: null,
|
||||||
|
originalVolume: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
function clearSleepTimer() {
|
||||||
|
if (sleepTimer.countdownInterval) clearInterval(sleepTimer.countdownInterval);
|
||||||
|
sleepTimer.countdownInterval = null;
|
||||||
|
sleepTimer.targetTime = 0;
|
||||||
|
sleepTimer.mode = null;
|
||||||
|
|
||||||
|
var badge = document.getElementById('yaktube-sleep-timer-badge');
|
||||||
|
if (badge) badge.remove();
|
||||||
|
var endListenerVideo = getVideoElement();
|
||||||
|
if (endListenerVideo && endListenerVideo._sleepEndListener) {
|
||||||
|
endListenerVideo.removeEventListener('ended', endListenerVideo._sleepEndListener);
|
||||||
|
endListenerVideo._sleepEndListener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSleepTimer(modeMinutes) {
|
||||||
|
clearSleepTimer();
|
||||||
|
var video = getVideoElement();
|
||||||
|
if (!video) {
|
||||||
|
announce('Aktif video bulunamadı.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sleepTimer.mode = modeMinutes;
|
||||||
|
sleepTimer.originalVolume = video.volume || 1;
|
||||||
|
|
||||||
|
if (modeMinutes === 'end') {
|
||||||
|
announce('Uyku zamanlayıcısı ayarlandı: Bu video bitince durdurulacak.', true);
|
||||||
|
showQuickToast('⏱️ Uyku: Video Bitince');
|
||||||
|
video._sleepEndListener = function () {
|
||||||
|
video.pause();
|
||||||
|
announce('Uyku zamanlayıcısı: Video bitti, oynatma durduruldu.', true);
|
||||||
|
clearSleepTimer();
|
||||||
|
};
|
||||||
|
video.addEventListener('ended', video._sleepEndListener, { once: true });
|
||||||
|
updateSleepTimerBadge('Video Bitince');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mins = parseInt(modeMinutes, 10);
|
||||||
|
if (isNaN(mins) || mins <= 0) return;
|
||||||
|
|
||||||
|
sleepTimer.targetTime = Date.now() + mins * 60 * 1000;
|
||||||
|
announce('Uyku zamanlayıcısı ' + mins + ' dakikaya ayarlandı.', true);
|
||||||
|
showQuickToast('⏱️ Uyku: ' + mins + ' Dakika');
|
||||||
|
|
||||||
|
function updateTick() {
|
||||||
|
var remainingMs = Math.max(0, sleepTimer.targetTime - Date.now());
|
||||||
|
var remainingSec = Math.floor(remainingMs / 1000);
|
||||||
|
var m = Math.floor(remainingSec / 60);
|
||||||
|
var s = remainingSec % 60;
|
||||||
|
var display = (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
|
||||||
|
updateSleepTimerBadge(display);
|
||||||
|
|
||||||
|
// Gentle volume fadeout in last 15 seconds
|
||||||
|
if (remainingSec <= 15 && remainingSec > 0 && video && !video.paused) {
|
||||||
|
var stepRatio = remainingSec / 15;
|
||||||
|
video.volume = Math.max(0, sleepTimer.originalVolume * stepRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingMs <= 0) {
|
||||||
|
clearSleepTimer();
|
||||||
|
if (video) {
|
||||||
|
video.pause();
|
||||||
|
video.volume = sleepTimer.originalVolume;
|
||||||
|
}
|
||||||
|
announce('Uyku zamanlayıcısı süresi doldu, video durduruldu.', true);
|
||||||
|
showQuickToast('⏱️ Uyku: Oynatma Durduruldu');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTick();
|
||||||
|
sleepTimer.countdownInterval = setInterval(updateTick, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSleepTimerBadge(text) {
|
||||||
|
var bar = document.getElementById('yaktube-player-action-bar');
|
||||||
|
if (!bar) return;
|
||||||
|
var badge = document.getElementById('yaktube-sleep-timer-badge');
|
||||||
|
if (!badge) {
|
||||||
|
badge = document.createElement('span');
|
||||||
|
badge.id = 'yaktube-sleep-timer-badge';
|
||||||
|
badge.className = 'yaktube-timer-badge';
|
||||||
|
var sleepBtn = document.getElementById('yaktube-bar-sleep-btn');
|
||||||
|
if (sleepBtn && sleepBtn.nextSibling) {
|
||||||
|
bar.insertBefore(badge, sleepBtn.nextSibling);
|
||||||
|
} else {
|
||||||
|
bar.appendChild(badge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
badge.textContent = '⏱️ ' + text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSleepTimerModal() {
|
||||||
|
var existing = document.getElementById('yaktube-sleep-modal');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
|
||||||
|
var backdrop = document.createElement('div');
|
||||||
|
backdrop.id = 'yaktube-sleep-modal';
|
||||||
|
backdrop.className = 'yaktube-feature-modal-backdrop';
|
||||||
|
backdrop.setAttribute('role', 'dialog');
|
||||||
|
backdrop.setAttribute('aria-modal', 'true');
|
||||||
|
backdrop.setAttribute('aria-label', 'Uyku Zamanlayıcısı');
|
||||||
|
|
||||||
|
var card = document.createElement('div');
|
||||||
|
card.className = 'yaktube-feature-modal-card';
|
||||||
|
|
||||||
|
var header = document.createElement('div');
|
||||||
|
header.className = 'yaktube-feature-modal-header';
|
||||||
|
header.innerHTML = '<h3 class="yaktube-feature-modal-title"><span>⏱️</span> Uyku Zamanlayıcısı</h3>' +
|
||||||
|
'<button class="yaktube-feature-modal-close" aria-label="Kapat (Escape)">×</button>';
|
||||||
|
|
||||||
|
var body = document.createElement('div');
|
||||||
|
body.className = 'yaktube-feature-modal-body';
|
||||||
|
body.innerHTML = '<p class="yaktube-qr-desc">Videonun ne zaman durdurulacağını seçin. Süre bittiğinde ses yavaşça kısılarak oynatma durdurulur.</p>' +
|
||||||
|
'<div class="yaktube-timer-options">' +
|
||||||
|
'<button class="yaktube-timer-btn' + (sleepTimer.mode === '15' ? ' active' : '') + '" data-mins="15">15 Dakika</button>' +
|
||||||
|
'<button class="yaktube-timer-btn' + (sleepTimer.mode === '30' ? ' active' : '') + '" data-mins="30">30 Dakika</button>' +
|
||||||
|
'<button class="yaktube-timer-btn' + (sleepTimer.mode === '45' ? ' active' : '') + '" data-mins="45">45 Dakika</button>' +
|
||||||
|
'<button class="yaktube-timer-btn' + (sleepTimer.mode === '60' ? ' active' : '') + '" data-mins="60">60 Dakika</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'<button class="yaktube-timer-btn' + (sleepTimer.mode === 'end' ? ' active' : '') + '" data-mins="end" style="width:100%;">🎬 Bu Video Bitince</button>' +
|
||||||
|
(sleepTimer.targetTime || sleepTimer.mode ? '<button id="yaktube-timer-cancel-btn" class="yaktube-timer-btn" style="background:#ef4444!important;color:#fff!important;border-color:#ef4444!important;">❌ Zamanlayıcıyı İptal Et</button>' : '');
|
||||||
|
|
||||||
|
card.appendChild(header);
|
||||||
|
card.appendChild(body);
|
||||||
|
backdrop.appendChild(card);
|
||||||
|
document.body.appendChild(backdrop);
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
backdrop.remove();
|
||||||
|
announce('Uyku zamanlayıcısı penceresi kapatıldı.');
|
||||||
|
}
|
||||||
|
|
||||||
|
header.querySelector('.yaktube-feature-modal-close').addEventListener('click', closeModal);
|
||||||
|
backdrop.addEventListener('click', function (e) {
|
||||||
|
if (e.target === backdrop) closeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
body.querySelectorAll('.yaktube-timer-options .yaktube-timer-btn, button[data-mins="end"]').forEach(function (b) {
|
||||||
|
b.addEventListener('click', function () {
|
||||||
|
var mins = this.getAttribute('data-mins');
|
||||||
|
setSleepTimer(mins);
|
||||||
|
closeModal();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var cancelBtn = body.querySelector('#yaktube-timer-cancel-btn');
|
||||||
|
if (cancelBtn) {
|
||||||
|
cancelBtn.addEventListener('click', function () {
|
||||||
|
clearSleepTimer();
|
||||||
|
announce('Uyku zamanlayıcısı iptal edildi.', true);
|
||||||
|
showQuickToast('⏱️ Zamanlayıcı İptal Edildi');
|
||||||
|
closeModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstBtn = body.querySelector('.yaktube-timer-btn');
|
||||||
|
if (firstBtn) firstBtn.focus();
|
||||||
|
announce('Uyku zamanlayıcısı penceresi açıldı. Seçeneklerden birini seçin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// YAKTUBE PHONE TRANSFER / QR CODE ENGINE (v1.5.0)
|
||||||
|
// ==========================================================================
|
||||||
|
function openPhoneTransferModal() {
|
||||||
|
var video = getVideoElement();
|
||||||
|
var currentSec = video ? Math.floor(video.currentTime || 0) : 0;
|
||||||
|
var currentUrl = new URL(window.location.href);
|
||||||
|
if (currentSec > 0) {
|
||||||
|
currentUrl.searchParams.set('start', currentSec + 's');
|
||||||
|
}
|
||||||
|
var targetUrl = currentUrl.toString();
|
||||||
|
|
||||||
|
var existing = document.getElementById('yaktube-qr-modal');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
|
||||||
|
var backdrop = document.createElement('div');
|
||||||
|
backdrop.id = 'yaktube-qr-modal';
|
||||||
|
backdrop.className = 'yaktube-feature-modal-backdrop';
|
||||||
|
backdrop.setAttribute('role', 'dialog');
|
||||||
|
backdrop.setAttribute('aria-modal', 'true');
|
||||||
|
backdrop.setAttribute('aria-label', 'Telefonda Devam Et');
|
||||||
|
|
||||||
|
var card = document.createElement('div');
|
||||||
|
card.className = 'yaktube-feature-modal-card';
|
||||||
|
|
||||||
|
var header = document.createElement('div');
|
||||||
|
header.className = 'yaktube-feature-modal-header';
|
||||||
|
header.innerHTML = '<h3 class="yaktube-feature-modal-title"><span>📱</span> Telefonda Devam Et</h3>' +
|
||||||
|
'<button class="yaktube-feature-modal-close" aria-label="Kapat (Escape)">×</button>';
|
||||||
|
|
||||||
|
var formattedTime = formatClockTime(currentSec);
|
||||||
|
var qrImageUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=220x220&margin=8&data=' + encodeURIComponent(targetUrl);
|
||||||
|
|
||||||
|
var body = document.createElement('div');
|
||||||
|
body.className = 'yaktube-feature-modal-body';
|
||||||
|
body.innerHTML =
|
||||||
|
'<p class="yaktube-qr-desc">Telefonunuzun kamerasıyla QR kodu okutarak videoyu kaldığınız yerden (<strong>' + formattedTime + '</strong>) izlemeye devam edebilirsiniz.</p>' +
|
||||||
|
'<div class="yaktube-qr-wrapper">' +
|
||||||
|
'<img src="' + qrImageUrl + '" alt="Video Bağlantısı QR Kodu" width="190" height="190" style="display:block;border-radius:8px;" />' +
|
||||||
|
'</div>' +
|
||||||
|
'<button id="yaktube-qr-copy-action-btn" class="yaktube-qr-copy-btn">📋 Bağlantıyı Kopyala</button>';
|
||||||
|
|
||||||
|
card.appendChild(header);
|
||||||
|
card.appendChild(body);
|
||||||
|
backdrop.appendChild(card);
|
||||||
|
document.body.appendChild(backdrop);
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
backdrop.remove();
|
||||||
|
announce('Telefonda devam et penceresi kapatıldı.');
|
||||||
|
}
|
||||||
|
|
||||||
|
header.querySelector('.yaktube-feature-modal-close').addEventListener('click', closeModal);
|
||||||
|
backdrop.addEventListener('click', function (e) {
|
||||||
|
if (e.target === backdrop) closeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
var copyBtn = body.querySelector('#yaktube-qr-copy-action-btn');
|
||||||
|
copyBtn.addEventListener('click', function () {
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
navigator.clipboard.writeText(targetUrl).then(function () {
|
||||||
|
copyBtn.textContent = '✅ Bağlantı Kopyalandı!';
|
||||||
|
announce('Bağlantı panoya kopyalandı: ' + targetUrl);
|
||||||
|
setTimeout(function () {
|
||||||
|
copyBtn.textContent = '📋 Bağlantıyı Kopyala';
|
||||||
|
}, 2500);
|
||||||
|
}).catch(function () {
|
||||||
|
fallbackCopy(targetUrl, copyBtn);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fallbackCopy(targetUrl, copyBtn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function fallbackCopy(text, btn) {
|
||||||
|
var tempInput = document.createElement('input');
|
||||||
|
tempInput.value = text;
|
||||||
|
document.body.appendChild(tempInput);
|
||||||
|
tempInput.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
btn.textContent = '✅ Bağlantı Kopyalandı!';
|
||||||
|
announce('Bağlantı panoya kopyalandı.');
|
||||||
|
setTimeout(function () {
|
||||||
|
btn.textContent = '📋 Bağlantıyı Kopyala';
|
||||||
|
}, 2500);
|
||||||
|
} catch (e) {
|
||||||
|
btn.textContent = '❌ Kopyalanamadı';
|
||||||
|
}
|
||||||
|
document.body.removeChild(tempInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyBtn.focus();
|
||||||
|
announce('Telefonda devam et penceresi açıldı. QR kodu okutabilir veya bağlantıyı kopyalayabilirsiniz.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// YAKTUBE DOUBLE-TAP TOUCH SEEK ENGINE (v1.5.0)
|
||||||
|
// ==========================================================================
|
||||||
|
function showDoubleTapRipple(container, isLeft) {
|
||||||
|
var className = isLeft ? 'yaktube-doubletap-left' : 'yaktube-doubletap-right';
|
||||||
|
var existing = container.querySelector('.yaktube-doubletap-overlay.' + className);
|
||||||
|
if (!existing) {
|
||||||
|
existing = document.createElement('div');
|
||||||
|
existing.className = 'yaktube-doubletap-overlay ' + className;
|
||||||
|
existing.innerHTML = '<div class="yaktube-ripple-content">' + (isLeft ? '⏪ 10sn Geri' : '10sn İleri ⏩') + '</div>';
|
||||||
|
container.appendChild(existing);
|
||||||
|
}
|
||||||
|
existing.classList.add('yaktube-ripple-active');
|
||||||
|
clearTimeout(existing._rippleTimer);
|
||||||
|
existing._rippleTimer = setTimeout(function () {
|
||||||
|
existing.classList.remove('yaktube-ripple-active');
|
||||||
|
}, 650);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTouchSeekGesture(video) {
|
||||||
|
if (!video) return;
|
||||||
|
var playerContainer = video.closest('.video-js, .player-container, .player, my-video-watch') || video.parentElement;
|
||||||
|
if (!playerContainer || playerContainer.hasAttribute('data-yaktube-touch-seek-hooked')) return;
|
||||||
|
playerContainer.setAttribute('data-yaktube-touch-seek-hooked', 'true');
|
||||||
|
|
||||||
|
var lastTapTime = 0;
|
||||||
|
var lastTapX = 0;
|
||||||
|
var tapTimeout = null;
|
||||||
|
|
||||||
|
playerContainer.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
function (e) {
|
||||||
|
if (e.changedTouches && e.changedTouches.length === 1) {
|
||||||
|
var touch = e.changedTouches[0];
|
||||||
|
var rect = playerContainer.getBoundingClientRect();
|
||||||
|
var tapX = touch.clientX - rect.left;
|
||||||
|
var tapY = touch.clientY - rect.top;
|
||||||
|
var now = Date.now();
|
||||||
|
|
||||||
|
if (tapY > rect.height - 50) return;
|
||||||
|
|
||||||
|
if (now - lastTapTime < 320 && Math.abs(tapX - lastTapX) < 60) {
|
||||||
|
e.preventDefault();
|
||||||
|
clearTimeout(tapTimeout);
|
||||||
|
lastTapTime = 0;
|
||||||
|
|
||||||
|
var isLeft = tapX < rect.width * 0.45;
|
||||||
|
var isRight = tapX > rect.width * 0.55;
|
||||||
|
|
||||||
|
if (isLeft) {
|
||||||
|
video.currentTime = Math.max(0, video.currentTime - 10);
|
||||||
|
showDoubleTapRipple(playerContainer, true);
|
||||||
|
announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime));
|
||||||
|
} else if (isRight) {
|
||||||
|
video.currentTime = Math.min(video.duration || 999999, video.currentTime + 10);
|
||||||
|
showDoubleTapRipple(playerContainer, false);
|
||||||
|
announce('10 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastTapTime = now;
|
||||||
|
lastTapX = tapX;
|
||||||
|
tapTimeout = setTimeout(function () {
|
||||||
|
lastTapTime = 0;
|
||||||
|
}, 330);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// YAKTUBE VOICE ASSISTANT COMMANDS HANDLER (v1.5.0)
|
||||||
|
// ==========================================================================
|
||||||
|
function handleVoiceAssistantCommand(rawTranscript) {
|
||||||
|
if (!rawTranscript) return false;
|
||||||
|
var text = rawTranscript.toLowerCase().trim();
|
||||||
|
var video = getVideoElement();
|
||||||
|
|
||||||
|
if (text === 'durdur' || text === 'duraklat' || text === 'videoyu durdur' || text === 'pause') {
|
||||||
|
if (video) video.pause();
|
||||||
|
announce('Video duraklatıldı.', true);
|
||||||
|
showQuickToast('⏸️ Video Duraklatıldı');
|
||||||
|
stopVoiceInsideModal();
|
||||||
|
closeFullSearchModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text === 'oynat' || text === 'başlat' || text === 'devam et' || text === 'videoyu oynat' || text === 'play') {
|
||||||
|
if (video) video.play();
|
||||||
|
announce('Video oynatılıyor.', true);
|
||||||
|
showQuickToast('▶️ Video Oynatılıyor');
|
||||||
|
stopVoiceInsideModal();
|
||||||
|
closeFullSearchModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text === 'tam ekran' || text === 'büyük ekran' || text === 'tam ekran yap') {
|
||||||
|
var playerWrap = document.querySelector('.player-container, .video-player-container, my-video-watch .player') || video;
|
||||||
|
if (playerWrap) {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
if (playerWrap.requestFullscreen) playerWrap.requestFullscreen();
|
||||||
|
announce('Tam ekran moduna geçildi.', true);
|
||||||
|
showQuickToast('⛶ Tam Ekran');
|
||||||
|
} else {
|
||||||
|
if (document.exitFullscreen) document.exitFullscreen();
|
||||||
|
announce('Tam ekrandan çıkıldı.', true);
|
||||||
|
showQuickToast('⛶ Normal Ekran');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stopVoiceInsideModal();
|
||||||
|
closeFullSearchModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text === 'sesi aç' || text === 'sesi yükselt' || text === 'sesi arttır' || text === 'ses aç') {
|
||||||
|
if (video) {
|
||||||
|
video.volume = Math.min(1, video.volume + 0.2);
|
||||||
|
var pct = Math.round(video.volume * 100);
|
||||||
|
announce('Ses seviyesi artırıldı: %' + pct, true);
|
||||||
|
showQuickToast('🔊 Ses: %' + pct);
|
||||||
|
}
|
||||||
|
stopVoiceInsideModal();
|
||||||
|
closeFullSearchModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text === 'sesi kıs' || text === 'sesi azalt' || text === 'ses kıs') {
|
||||||
|
if (video) {
|
||||||
|
video.volume = Math.max(0, video.volume - 0.2);
|
||||||
|
var pct = Math.round(video.volume * 100);
|
||||||
|
announce('Ses seviyesi azaltıldı: %' + pct, true);
|
||||||
|
showQuickToast('🔉 Ses: %' + pct);
|
||||||
|
}
|
||||||
|
stopVoiceInsideModal();
|
||||||
|
closeFullSearchModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text === 'sessiz' || text === 'sustur' || text === 'sesi kapat' || text === 'mute') {
|
||||||
|
if (video) {
|
||||||
|
video.muted = !video.muted;
|
||||||
|
announce(video.muted ? 'Ses kapatıldı.' : 'Ses açıldı.', true);
|
||||||
|
showQuickToast(video.muted ? '🔇 Sessiz' : '🔊 Ses Açık');
|
||||||
|
}
|
||||||
|
stopVoiceInsideModal();
|
||||||
|
closeFullSearchModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text === 'ileri sar' || text === '10 saniye ileri' || text === 'ileri al' || text === 'atla') {
|
||||||
|
if (video) {
|
||||||
|
video.currentTime = Math.min(video.duration || 999999, video.currentTime + 10);
|
||||||
|
announce('10 saniye ileri sarıldı.', true);
|
||||||
|
showQuickToast('⏩ +10sn İleri');
|
||||||
|
}
|
||||||
|
stopVoiceInsideModal();
|
||||||
|
closeFullSearchModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text === 'geri sar' || text === '10 saniye geri' || text === 'geri al') {
|
||||||
|
if (video) {
|
||||||
|
video.currentTime = Math.max(0, video.currentTime - 10);
|
||||||
|
announce('10 saniye geri sarıldı.', true);
|
||||||
|
showQuickToast('⏪ -10sn Geri');
|
||||||
|
}
|
||||||
|
stopVoiceInsideModal();
|
||||||
|
closeFullSearchModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.includes('uyku') || text.includes('zamanlayıcı')) {
|
||||||
|
stopVoiceInsideModal();
|
||||||
|
closeFullSearchModal();
|
||||||
|
openSleepTimerModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text === 'kapat' || text === 'çık' || text === 'iptal') {
|
||||||
|
stopVoiceInsideModal();
|
||||||
|
closeFullSearchModal();
|
||||||
|
announce('Arama penceresi kapatıldı.');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 3.1. Injects Accessible Player Action Bar under the Watch Video
|
// 3.1. Injects Accessible Player Action Bar under the Watch Video
|
||||||
function injectAccessiblePlayerBar() {
|
function injectAccessiblePlayerBar() {
|
||||||
if (!window.location.pathname.includes('/videos/watch/')) return;
|
if (!window.location.pathname.includes('/videos/watch/')) return;
|
||||||
@@ -1169,6 +1660,8 @@ try {
|
|||||||
'<button id="yaktube-bar-play-btn" class="yaktube-player-action-btn" aria-label="Oynat veya Duraklat (Alt+K)">⏯️ Oynat/Duraklat</button>' +
|
'<button id="yaktube-bar-play-btn" class="yaktube-player-action-btn" aria-label="Oynat veya Duraklat (Alt+K)">⏯️ Oynat/Duraklat</button>' +
|
||||||
'<button id="yaktube-bar-forward-btn" class="yaktube-player-action-btn" aria-label="10 Saniye İleri Sar (Alt+L)">⏩ 10sn İleri</button>' +
|
'<button id="yaktube-bar-forward-btn" class="yaktube-player-action-btn" aria-label="10 Saniye İleri Sar (Alt+L)">⏩ 10sn İleri</button>' +
|
||||||
'<button id="yaktube-bar-speed-btn" class="yaktube-player-action-btn" aria-label="Oynatma Hızını Değiştir (Alt+.)">⚡ Hız</button>' +
|
'<button id="yaktube-bar-speed-btn" class="yaktube-player-action-btn" aria-label="Oynatma Hızını Değiştir (Alt+.)">⚡ Hız</button>' +
|
||||||
|
'<button id="yaktube-bar-sleep-btn" class="yaktube-player-action-btn" aria-label="Uyku Zamanlayıcısı (Alt+U)">⏱️ Uyku</button>' +
|
||||||
|
'<button id="yaktube-bar-qr-btn" class="yaktube-player-action-btn" aria-label="Telefonda Devam Et / QR Kod (Alt+Q)">📱 Telefonda Aç</button>' +
|
||||||
'<button id="yaktube-bar-theater-btn" class="yaktube-player-action-btn" aria-label="Sinema Modunu Aç/Kapat (Alt+T)">🎭 Sinema</button>' +
|
'<button id="yaktube-bar-theater-btn" class="yaktube-player-action-btn" aria-label="Sinema Modunu Aç/Kapat (Alt+T)">🎭 Sinema</button>' +
|
||||||
'<button id="yaktube-bar-info-btn" class="yaktube-player-action-btn" aria-label="Video Bilgisi ve İstatistikleri (Alt+I)">📊 Bilgi</button>' +
|
'<button id="yaktube-bar-info-btn" class="yaktube-player-action-btn" aria-label="Video Bilgisi ve İstatistikleri (Alt+I)">📊 Bilgi</button>' +
|
||||||
'<button id="yaktube-bar-help-btn" class="yaktube-player-action-btn" aria-label="Kısayol Kılavuzu (Alt+H veya F1)">⌨️ Kısayollar</button>';
|
'<button id="yaktube-bar-help-btn" class="yaktube-player-action-btn" aria-label="Kısayol Kılavuzu (Alt+H veya F1)">⌨️ Kısayollar</button>';
|
||||||
@@ -1213,6 +1706,20 @@ try {
|
|||||||
adjustPlaybackSpeed(true);
|
adjustPlaybackSpeed(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var sleepBtn = bar.querySelector('#yaktube-bar-sleep-btn');
|
||||||
|
if (sleepBtn) {
|
||||||
|
sleepBtn.addEventListener('click', function () {
|
||||||
|
openSleepTimerModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var qrBtn = bar.querySelector('#yaktube-bar-qr-btn');
|
||||||
|
if (qrBtn) {
|
||||||
|
qrBtn.addEventListener('click', function () {
|
||||||
|
openPhoneTransferModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
bar.querySelector('#yaktube-bar-theater-btn').addEventListener('click', function () {
|
bar.querySelector('#yaktube-bar-theater-btn').addEventListener('click', function () {
|
||||||
document.body.classList.toggle('yaktube-theater-mode');
|
document.body.classList.toggle('yaktube-theater-mode');
|
||||||
var isT = document.body.classList.contains('yaktube-theater-mode');
|
var isT = document.body.classList.contains('yaktube-theater-mode');
|
||||||
@@ -1231,6 +1738,18 @@ try {
|
|||||||
// 4. Global Keydown Listener (Screen-Reader & Turkish Q Optimized)
|
// 4. Global Keydown Listener (Screen-Reader & Turkish Q Optimized)
|
||||||
window.addEventListener('keydown', function (e) {
|
window.addEventListener('keydown', function (e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
|
var sleepModal = document.getElementById('yaktube-sleep-modal');
|
||||||
|
if (sleepModal) {
|
||||||
|
sleepModal.remove();
|
||||||
|
announce('Uyku zamanlayıcısı penceresi kapatıldı.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var qrModal = document.getElementById('yaktube-qr-modal');
|
||||||
|
if (qrModal) {
|
||||||
|
qrModal.remove();
|
||||||
|
announce('Telefonda devam et penceresi kapatıldı.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
var shortcutsModal = document.getElementById('yaktube-shortcuts-dialog');
|
var shortcutsModal = document.getElementById('yaktube-shortcuts-dialog');
|
||||||
if (shortcutsModal && shortcutsModal.style.display !== 'none') {
|
if (shortcutsModal && shortcutsModal.style.display !== 'none') {
|
||||||
shortcutsModal.style.display = 'none';
|
shortcutsModal.style.display = 'none';
|
||||||
@@ -1256,6 +1775,20 @@ try {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sleep Timer: Alt+U
|
||||||
|
if (e.altKey && (e.key === 'u' || e.key === 'U') && !isInputFocused()) {
|
||||||
|
e.preventDefault();
|
||||||
|
openSleepTimerModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phone QR Transfer: Alt+Q
|
||||||
|
if (e.altKey && (e.key === 'q' || e.key === 'Q') && !isInputFocused()) {
|
||||||
|
e.preventDefault();
|
||||||
|
openPhoneTransferModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Help Dialog: Alt+H, F1 or '?' key
|
// Help Dialog: Alt+H, F1 or '?' key
|
||||||
if ((e.altKey && (e.key === 'h' || e.key === 'H')) || e.key === 'F1' || e.key === '?') {
|
if ((e.altKey && (e.key === 'h' || e.key === 'H')) || e.key === 'F1' || e.key === '?') {
|
||||||
if (!isInputFocused()) {
|
if (!isInputFocused()) {
|
||||||
@@ -1973,20 +2506,31 @@ try {
|
|||||||
announce(query + ' için arama başlatıldı.');
|
announce(query + ' için arama başlatıldı.');
|
||||||
isSearching = true;
|
isSearching = true;
|
||||||
|
|
||||||
|
if (window.__yaktube_search_abort_controller__) {
|
||||||
|
try {
|
||||||
|
window.__yaktube_search_abort_controller__.abort();
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
var abortCtrl = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
||||||
|
window.__yaktube_search_abort_controller__ = abortCtrl;
|
||||||
|
var fetchOpts = abortCtrl ? { signal: abortCtrl.signal } : {};
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
fetch('/api-custom/youtube-search?q=' + encodeURIComponent(query))
|
fetch('/api-custom/youtube-search?q=' + encodeURIComponent(query), fetchOpts)
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
|
if (err && err.name === 'AbortError') return { results: [] };
|
||||||
console.warn('[YakTube] YouTube search fetch error:', err);
|
console.warn('[YakTube] YouTube search fetch error:', err);
|
||||||
return { results: [] };
|
return { results: [] };
|
||||||
}),
|
}),
|
||||||
fetch('/api/v1/search/videos?search=' + encodeURIComponent(query) + '&isLocal=true&count=12')
|
fetch('/api/v1/search/videos?search=' + encodeURIComponent(query) + '&isLocal=true&count=12', fetchOpts)
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
|
if (err && err.name === 'AbortError') return { total: 0, data: [] };
|
||||||
console.warn('[YakTube] Local videos search fetch error:', err);
|
console.warn('[YakTube] Local videos search fetch error:', err);
|
||||||
return { total: 0, data: [] };
|
return { total: 0, data: [] };
|
||||||
})
|
})
|
||||||
@@ -2800,6 +3344,9 @@ try {
|
|||||||
if (inp) inp.value = transcript;
|
if (inp) inp.value = transcript;
|
||||||
|
|
||||||
if (ev.results[0].isFinal) {
|
if (ev.results[0].isFinal) {
|
||||||
|
if (handleVoiceAssistantCommand(transcript)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
announce('"' + transcript + '" için arama başlatılıyor...', true);
|
announce('"' + transcript + '" için arama başlatılıyor...', true);
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
closeFullSearchModal();
|
closeFullSearchModal();
|
||||||
@@ -3636,28 +4183,7 @@ try {
|
|||||||
menuContainer.appendChild(sidebarLink);
|
menuContainer.appendChild(sidebarLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(function () {
|
// 9. Background Play (Screen-Off / Minimize) & MediaSession Lock-Screen Controls
|
||||||
checkAndInjectSearchResults();
|
|
||||||
injectSearchEnhancements();
|
|
||||||
checkAndInjectRelatedVideos(false);
|
|
||||||
restoreActiveLiveStream();
|
|
||||||
checkAndInjectWatchPageComments();
|
|
||||||
injectPWAInstallButton();
|
|
||||||
injectApkDownloadButton();
|
|
||||||
injectSidebarApkLink();
|
|
||||||
}, 800);
|
|
||||||
restoreActiveLiveStream();
|
|
||||||
window.addEventListener('popstate', function () {
|
|
||||||
currentActiveVideoKey = null;
|
|
||||||
checkAndInjectSearchResults();
|
|
||||||
checkAndInjectRelatedVideos(true);
|
|
||||||
checkAndInjectWatchPageComments();
|
|
||||||
});
|
|
||||||
checkAndInjectWatchPageComments();
|
|
||||||
})();
|
|
||||||
|
|
||||||
// 5. Background Play (Screen-Off / Minimize) & MediaSession Lock-Screen Controls
|
|
||||||
(function initBackgroundPlaybackEngine() {
|
|
||||||
var videoState = {
|
var videoState = {
|
||||||
hasPlayed: false,
|
hasPlayed: false,
|
||||||
explicitlyPausedByClick: false,
|
explicitlyPausedByClick: false,
|
||||||
@@ -3668,6 +4194,10 @@ try {
|
|||||||
if (!video || video.hasAttribute('data-yaktube-bgplay-hooked')) return;
|
if (!video || video.hasAttribute('data-yaktube-bgplay-hooked')) return;
|
||||||
video.setAttribute('data-yaktube-bgplay-hooked', 'true');
|
video.setAttribute('data-yaktube-bgplay-hooked', 'true');
|
||||||
|
|
||||||
|
try {
|
||||||
|
initTouchSeekGesture(video);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
video.addEventListener('play', function () {
|
video.addEventListener('play', function () {
|
||||||
videoState.hasPlayed = true;
|
videoState.hasPlayed = true;
|
||||||
videoState.explicitlyPausedByClick = false;
|
videoState.explicitlyPausedByClick = false;
|
||||||
@@ -3791,11 +4321,127 @@ try {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-hook active video elements
|
// 10. Unified Debounced MutationObserver & Reactive SPA Engine (v1.5.0)
|
||||||
setInterval(function () {
|
var unifiedTimer = null;
|
||||||
|
function runUnifiedCycle(isNav) {
|
||||||
|
try {
|
||||||
|
autoFixDOM();
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
var video = getVideoElement();
|
var video = getVideoElement();
|
||||||
if (video) attachVideoListeners(video);
|
if (video) attachVideoListeners(video);
|
||||||
}, 1000);
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
checkAndInjectSearchResults();
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
injectSearchEnhancements();
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
checkAndInjectRelatedVideos(Boolean(isNav));
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
restoreActiveLiveStream();
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
checkAndInjectWatchPageComments();
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
injectPWAInstallButton();
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
injectApkDownloadButton();
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
injectSidebarApkLink();
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleUnifiedCycle(immediate, debounceDelay) {
|
||||||
|
if (unifiedTimer) {
|
||||||
|
clearTimeout(unifiedTimer);
|
||||||
|
unifiedTimer = null;
|
||||||
|
}
|
||||||
|
if (immediate) {
|
||||||
|
runUnifiedCycle(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var delay = typeof debounceDelay === 'number' ? debounceDelay : 150;
|
||||||
|
unifiedTimer = setTimeout(function () {
|
||||||
|
unifiedTimer = null;
|
||||||
|
runUnifiedCycle(false);
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPA Navigation Handler
|
||||||
|
function onLocationChange() {
|
||||||
|
currentActiveVideoKey = null;
|
||||||
|
scheduleUnifiedCycle(true);
|
||||||
|
setTimeout(function () {
|
||||||
|
scheduleUnifiedCycle(false, 0);
|
||||||
|
}, 350);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('popstate', onLocationChange);
|
||||||
|
|
||||||
|
// History pushState / replaceState interception for seamless Angular routing
|
||||||
|
(function hookHistory() {
|
||||||
|
try {
|
||||||
|
var origPush = history.pushState;
|
||||||
|
var origReplace = history.replaceState;
|
||||||
|
if (origPush) {
|
||||||
|
history.pushState = function () {
|
||||||
|
var ret = origPush.apply(this, arguments);
|
||||||
|
try {
|
||||||
|
onLocationChange();
|
||||||
|
} catch (e) {}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (origReplace) {
|
||||||
|
history.replaceState = function () {
|
||||||
|
var ret = origReplace.apply(this, arguments);
|
||||||
|
try {
|
||||||
|
onLocationChange();
|
||||||
|
} catch (e) {}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// MutationObserver: react immediately to DOM changes instead of CPU polling
|
||||||
|
if (typeof MutationObserver !== 'undefined') {
|
||||||
|
try {
|
||||||
|
var observer = new MutationObserver(function () {
|
||||||
|
scheduleUnifiedCycle(false, 150);
|
||||||
|
});
|
||||||
|
if (document.body) {
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gentle 3.5s heartbeat fallback (replaces aggressive 800ms polling, reduces idle CPU by 80%)
|
||||||
|
setInterval(function () {
|
||||||
|
scheduleUnifiedCycle(false, 0);
|
||||||
|
}, 3500);
|
||||||
|
|
||||||
|
// Initial trigger
|
||||||
|
runUnifiedCycle(true);
|
||||||
})();
|
})();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[YakTube] Safe catch error:', err);
|
console.error('[YakTube] Safe catch error:', err);
|
||||||
|
|||||||
@@ -1,10 +1,101 @@
|
|||||||
const { execFile } = require('child_process');
|
const { execFile } = require('child_process');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
const searchCache = new Map();
|
const searchCache = new Map();
|
||||||
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
|
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
|
||||||
|
|
||||||
|
let cachedYtDlpPath = null;
|
||||||
|
function resolveYtDlpPath(logger) {
|
||||||
|
if (cachedYtDlpPath && (cachedYtDlpPath === 'yt-dlp' || fs.existsSync(cachedYtDlpPath))) {
|
||||||
|
return cachedYtDlpPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Environment variable override
|
||||||
|
if (process.env.YTDLP_PATH && fs.existsSync(process.env.YTDLP_PATH)) {
|
||||||
|
cachedYtDlpPath = process.env.YTDLP_PATH;
|
||||||
|
return cachedYtDlpPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Cross-platform candidates
|
||||||
|
const isWin = process.platform === 'win32';
|
||||||
|
const candidates = isWin
|
||||||
|
? [
|
||||||
|
'D:\\yaktube_storage\\bin\\yt-dlp.exe',
|
||||||
|
'C:\\laragon\\bin\\yt-dlp.exe',
|
||||||
|
path.join(process.cwd(), 'bin', 'yt-dlp.exe'),
|
||||||
|
'yt-dlp.exe'
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'/usr/local/bin/yt-dlp',
|
||||||
|
'/usr/bin/yt-dlp',
|
||||||
|
'/bin/yt-dlp',
|
||||||
|
path.join(process.cwd(), 'bin', 'yt-dlp'),
|
||||||
|
'yt-dlp'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
try {
|
||||||
|
if (candidate.includes(path.sep) && fs.existsSync(candidate)) {
|
||||||
|
cachedYtDlpPath = candidate;
|
||||||
|
if (logger) logger.info(`[YakTube OnDemand] Auto-detected yt-dlp binary at: ${cachedYtDlpPath}`);
|
||||||
|
return cachedYtDlpPath;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedYtDlpPath = isWin ? 'D:\\yaktube_storage\\bin\\yt-dlp.exe' : 'yt-dlp';
|
||||||
|
return cachedYtDlpPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedChannels = null;
|
||||||
|
let channelsExpiresAt = 0;
|
||||||
|
|
||||||
|
async function getDefaultChannelId(category) {
|
||||||
|
// Category mapping defaults
|
||||||
|
const cat = String(category || '').toLowerCase();
|
||||||
|
if (cat.includes('tekno') || cat.includes('tech') || cat.includes('egitim')) return '3299';
|
||||||
|
if (cat.includes('podcast') || cat.includes('sohbet')) return '3298';
|
||||||
|
if (cat.includes('genel') || cat.includes('haber')) return '1';
|
||||||
|
if (cat.includes('muzik') || cat.includes('music')) return '1146';
|
||||||
|
|
||||||
|
if (cachedChannels && Date.now() < channelsExpiresAt) {
|
||||||
|
return cachedChannels.defaultId || '1146';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const req = http.request(
|
||||||
|
{
|
||||||
|
hostname: '127.0.0.1',
|
||||||
|
port: 9000,
|
||||||
|
path: '/api/v1/accounts/yaktube/video-channels',
|
||||||
|
method: 'GET',
|
||||||
|
headers: { Host: 'yaktube.yakhub.com.tr' }
|
||||||
|
},
|
||||||
|
res => {
|
||||||
|
let body = '';
|
||||||
|
res.on('data', c => (body += c));
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(body);
|
||||||
|
if (data && Array.isArray(data.data) && data.data.length > 0) {
|
||||||
|
const musicCh = data.data.find(c => c.name && c.name.includes('muzik'));
|
||||||
|
const defaultId = musicCh ? String(musicCh.id) : String(data.data[0].id);
|
||||||
|
cachedChannels = { defaultId, list: data.data };
|
||||||
|
channelsExpiresAt = Date.now() + 60 * 60 * 1000;
|
||||||
|
return resolve(defaultId);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
resolve('1146');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
req.on('error', () => resolve('1146'));
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getCached(q) {
|
function getCached(q) {
|
||||||
const item = searchCache.get(q);
|
const item = searchCache.get(q);
|
||||||
if (item && Date.now() - item.time < CACHE_TTL) {
|
if (item && Date.now() - item.time < CACHE_TTL) {
|
||||||
@@ -99,7 +190,7 @@ async function getAdminToken() {
|
|||||||
|
|
||||||
async function register({ getRouter, peertubeHelpers, logger }) {
|
async function register({ getRouter, peertubeHelpers, logger }) {
|
||||||
const router = getRouter();
|
const router = getRouter();
|
||||||
const ytdlpPath = 'D:\\yaktube_storage\\bin\\yt-dlp.exe';
|
const ytdlpPath = resolveYtDlpPath(logger);
|
||||||
|
|
||||||
router.get('/search', async (req, res) => {
|
router.get('/search', async (req, res) => {
|
||||||
const query = (req.query.q || '').trim();
|
const query = (req.query.q || '').trim();
|
||||||
@@ -157,16 +248,17 @@ async function register({ getRouter, peertubeHelpers, logger }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('/import', async (req, res) => {
|
router.post('/import', async (req, res) => {
|
||||||
const { targetUrl } = req.body;
|
const { targetUrl, channelId, category } = req.body;
|
||||||
if (!targetUrl || (!targetUrl.includes('youtube.com') && !targetUrl.includes('youtu.be'))) {
|
if (!targetUrl || (!targetUrl.includes('youtube.com') && !targetUrl.includes('youtu.be'))) {
|
||||||
return res.status(400).json({ error: 'Invalid YouTube URL' });
|
return res.status(400).json({ error: 'Invalid YouTube URL' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = await getAdminToken();
|
const token = await getAdminToken();
|
||||||
|
const resolvedChannelId = channelId ? String(channelId) : await getDefaultChannelId(category);
|
||||||
const postData = new URLSearchParams({
|
const postData = new URLSearchParams({
|
||||||
targetUrl: targetUrl,
|
targetUrl: targetUrl,
|
||||||
channelId: '1146', // Türkçe Müzik channel
|
channelId: resolvedChannelId,
|
||||||
privacy: '1'
|
privacy: '1'
|
||||||
}).toString();
|
}).toString();
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "peertube-plugin-yaktube-ondemand",
|
"name": "peertube-plugin-yaktube-ondemand",
|
||||||
"version": "1.4.33",
|
"version": "1.5.0",
|
||||||
"description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations and A11y Engine for PeerTube",
|
"description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations, Sleep Timer, Touch Gestures, Voice Assistant & A11y Engine for PeerTube",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"library": "./main.js",
|
"library": "./main.js",
|
||||||
"staticDirs": {},
|
"staticDirs": {},
|
||||||
@@ -30,6 +30,10 @@
|
|||||||
"accessibility",
|
"accessibility",
|
||||||
"a11y",
|
"a11y",
|
||||||
"screen-reader",
|
"screen-reader",
|
||||||
|
"sleep-timer",
|
||||||
|
"touch-gestures",
|
||||||
|
"voice-assistant",
|
||||||
|
"qr-transfer",
|
||||||
"fediverse",
|
"fediverse",
|
||||||
"voice-search",
|
"voice-search",
|
||||||
"speech-recognition",
|
"speech-recognition",
|
||||||
|
|||||||
@@ -1097,3 +1097,246 @@ body:has(#yaktube-ondemand-container) my-error-page {
|
|||||||
outline: 2px solid #ff8f37;
|
outline: 2px solid #ff8f37;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* YAKTUBE TOUCH & MOBILE DOUBLE-TAP TO SEEK (v1.5.0) */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
.yaktube-doubletap-overlay {
|
||||||
|
position: absolute !important;
|
||||||
|
top: 0 !important;
|
||||||
|
bottom: 0 !important;
|
||||||
|
width: 40% !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s ease-out !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-doubletap-left {
|
||||||
|
left: 0 !important;
|
||||||
|
background: radial-gradient(circle at left center, rgba(255, 143, 55, 0.35) 0%, transparent 70%) !important;
|
||||||
|
border-top-left-radius: 16px;
|
||||||
|
border-bottom-left-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-doubletap-right {
|
||||||
|
right: 0 !important;
|
||||||
|
background: radial-gradient(circle at right center, rgba(255, 143, 55, 0.35) 0%, transparent 70%) !important;
|
||||||
|
border-top-right-radius: 16px;
|
||||||
|
border-bottom-right-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-doubletap-overlay.yaktube-ripple-active {
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-ripple-content {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
background: rgba(0, 0, 0, 0.75) !important;
|
||||||
|
border: 1px solid rgba(255, 143, 55, 0.6) !important;
|
||||||
|
border-radius: 50px !important;
|
||||||
|
padding: 12px 24px !important;
|
||||||
|
color: #fff !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.6) !important;
|
||||||
|
transform: scale(0.85);
|
||||||
|
animation: yaktube-ripple-bounce 0.45s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes yaktube-ripple-bounce {
|
||||||
|
0% { transform: scale(0.7); opacity: 0.5; }
|
||||||
|
50% { transform: scale(1.1); opacity: 1; }
|
||||||
|
100% { transform: scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* YAKTUBE SLEEP TIMER & QR CODE MODALS (v1.5.0) */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
.yaktube-feature-modal-backdrop {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0 !important;
|
||||||
|
left: 0 !important;
|
||||||
|
width: 100vw !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
background: rgba(0, 0, 0, 0.82) !important;
|
||||||
|
backdrop-filter: blur(10px) !important;
|
||||||
|
z-index: 999999 !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
padding: 16px !important;
|
||||||
|
animation: yaktube-fade-in 0.2s ease-out !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-feature-modal-card {
|
||||||
|
background: #18181f !important;
|
||||||
|
border: 1px solid rgba(255, 143, 55, 0.4) !important;
|
||||||
|
border-radius: 20px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 440px !important;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.85) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
animation: yaktube-slide-up 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-feature-modal-header {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: space-between !important;
|
||||||
|
padding: 18px 24px !important;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||||
|
background: rgba(255, 143, 55, 0.08) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-feature-modal-title {
|
||||||
|
margin: 0 !important;
|
||||||
|
font-size: 18px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
color: #ff8f37 !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-feature-modal-close {
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
color: #94a3b8 !important;
|
||||||
|
font-size: 22px !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
padding: 4px 8px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-feature-modal-close:hover,
|
||||||
|
.yaktube-feature-modal-close:focus {
|
||||||
|
color: #fff !important;
|
||||||
|
background: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
outline: 2px solid #ff8f37 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-feature-modal-body {
|
||||||
|
padding: 24px !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
gap: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-timer-options {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: 1fr 1fr !important;
|
||||||
|
gap: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-timer-btn {
|
||||||
|
background: #23232c !important;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12) !important;
|
||||||
|
color: #f1f5f9 !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
padding: 14px 12px !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-timer-btn:hover,
|
||||||
|
.yaktube-timer-btn:focus-visible {
|
||||||
|
background: #2d2d38 !important;
|
||||||
|
border-color: #ff8f37 !important;
|
||||||
|
color: #ff8f37 !important;
|
||||||
|
transform: translateY(-2px) !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 143, 55, 0.25) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-timer-btn.active {
|
||||||
|
background: linear-gradient(135deg, #ff8f37 0%, #ff5e3a 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border-color: transparent !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
box-shadow: 0 4px 15px rgba(255, 143, 55, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-timer-badge {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
background: rgba(255, 143, 55, 0.2) !important;
|
||||||
|
border: 1px solid #ff8f37 !important;
|
||||||
|
color: #ff8f37 !important;
|
||||||
|
padding: 4px 10px !important;
|
||||||
|
border-radius: 20px !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
animation: yaktube-pulse 2s infinite !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes yaktube-pulse {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.04); }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-qr-wrapper {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
padding: 16px !important;
|
||||||
|
background: #ffffff !important;
|
||||||
|
border-radius: 16px !important;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4) !important;
|
||||||
|
width: fit-content !important;
|
||||||
|
margin: 0 auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-qr-wrapper svg {
|
||||||
|
display: block !important;
|
||||||
|
width: 190px !important;
|
||||||
|
height: 190px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-qr-desc {
|
||||||
|
font-size: 13px !important;
|
||||||
|
color: #94a3b8 !important;
|
||||||
|
text-align: center !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
line-height: 1.5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-qr-copy-btn {
|
||||||
|
background: linear-gradient(135deg, #ff8f37 0%, #ff5e3a 100%) !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
border: none !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
padding: 12px 20px !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
gap: 8px !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaktube-qr-copy-btn:hover,
|
||||||
|
.yaktube-qr-copy-btn:focus-visible {
|
||||||
|
transform: translateY(-2px) !important;
|
||||||
|
box-shadow: 0 6px 20px rgba(255, 143, 55, 0.4) !important;
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user