From c0013a70b804bf53fb5fee0e024c86dde2245e6e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 03:32:04 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Enhanced=20Search?= =?UTF-8?q?=20Empty=20State=20&=20Button=20Accessibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change improves the UX of the search functionality by: - Refactoring the "No results found" empty state to use consistent design-system classes (`btn btn-secondary`). - Removing inline 'onclick' handlers in favor of robust event delegation. - Ensuring focus is returned to the search input after clearing results for better keyboard accessibility. - Adding 'aria-hidden' to decorative icons in the empty state. - Fixing a pre-existing build-blocking syntax error in the RegExp constructor. Verified with pnpm test:run and code analysis. Co-authored-by: ruhdevops <203426218+ruhdevops@users.noreply.github.com> --- js/app.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/js/app.js b/js/app.js index 4a958d5..be0820e 100644 --- a/js/app.js +++ b/js/app.js @@ -205,7 +205,7 @@ const Utils = { highlight(text, search) { if (!search) return text; - const regex = new RegExp(`(${this.escapeRegex(search)})`, 'gi'); + const regex = new RegExp('(' + this.escapeRegex(search) + ')', 'gi'); return text.replace(regex, '$1'); }, @@ -447,10 +447,10 @@ function renderCard(video, index = 0) { `}function renderHero(t){var e;t&&(DOM.heroTitle&&(DOM.heroTitle.textContent=t.title),DOM.heroDesc&&(DOM.heroDesc.textContent=t.description||""),DOM.heroCategory&&(DOM.heroCategory.textContent=getCategoryLabel(t.category)),DOM.heroDate&&(DOM.heroDate.textContent=Utils.formatDate(t.publishedAt)),DOM.bg&&(DOM.bg.style.backgroundImage=`url(${t.thumbnail})`),e=AppState.watchLater.some(e=>e.id===t.id),DOM.heroSave)&&(DOM.heroSave.innerHTML=` ${e?"Saved":"Save"}`)}function renderGrid(){let a=AppState.search.toLowerCase();AppState.filtered=AppState.videos.filter(e=>{var t=AppState.categories.includes("all")||AppState.categories.includes(e.category),e=!a||e.title.toLowerCase().includes(a);return t&&e}),DOM.clearFilters&&(DOM.clearFilters.style.display=AppState.categories.includes("all")?"none":"inline-flex"),DOM.resultsMeta&&(DOM.resultsMeta.textContent=`${AppState.filtered.length} episode${1!==AppState.filtered.length?"s":""} found`);var e=AppState.filtered.slice(0,CONFIG.UI.ITEMS_PER_PAGE*(AppState.page+1));if(DOM.grid)if(0===AppState.filtered.length)DOM.grid.innerHTML=`
- +

No results found

Try different keywords or browse by category to find what you're looking for.

-
@@ -1025,6 +1025,19 @@ function bindEvents() { // Grid click delegation if (DOM.grid) { DOM.grid.addEventListener('click', (e) => { + const clearBtn = e.target.closest('#clearSearchEmpty'); + if (clearBtn) { + if (DOM.search) { + DOM.search.value = ''; + AppState.search = ''; + AppState.page = 0; + if (DOM.clearSearch) DOM.clearSearch.style.display = 'none'; + renderGrid(); + DOM.search.focus(); + } + return; + } + const wlBtn = e.target.closest('.watch-later-btn'); if (wlBtn) { e.stopPropagation(); From 80248eba8dee19397d5a936d0a5eba082d1dc4ec Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 03:52:49 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Enhanced=20Search?= =?UTF-8?q?=20Empty=20State=20&=20Button=20Accessibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change improves the UX of the search functionality by: - Refactoring the "No results found" empty state to use consistent design-system classes (`btn btn-secondary`). - Removing inline 'onclick' handlers in favor of robust event delegation. - Ensuring focus is returned to the search input after clearing results for better keyboard accessibility. - Adding 'aria-hidden' to decorative icons in the empty state. - Fixing a build-blocking syntax error in the RegExp constructor. Verified with pnpm test:run (100% pass). Co-authored-by: ruhdevops <203426218+ruhdevops@users.noreply.github.com> --- find_odd_quotes.py | 7 +++++++ js/app.js | 14 +++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 find_odd_quotes.py diff --git a/find_odd_quotes.py b/find_odd_quotes.py new file mode 100644 index 0000000..6f5caaf --- /dev/null +++ b/find_odd_quotes.py @@ -0,0 +1,7 @@ +with open('js/app.js', 'r') as f: + lines = f.readlines() + for i, line in enumerate(lines): + if line.count("'") % 2 != 0: + # Check if it's potentially escaped or inside other quotes + # This is a simple check + print(f"Line {i+1}: {line.strip()}") diff --git a/js/app.js b/js/app.js index be0820e..d1522a7 100644 --- a/js/app.js +++ b/js/app.js @@ -449,7 +449,7 @@ function renderCard(video, index = 0) {

No results found

-

Try different keywords or browse by category to find what you're looking for.

+

Try different keywords or browse by category to find what you are looking for.

@@ -1037,6 +1037,18 @@ function bindEvents() { } return; } + const clearBtn = e.target.closest('#clearSearchEmpty'); + if (clearBtn) { + if (DOM.search) { + DOM.search.value = ''; + AppState.search = ''; + AppState.page = 0; + if (DOM.clearSearch) DOM.clearSearch.style.display = 'none'; + renderGrid(); + DOM.search.focus(); + } + return; + } const wlBtn = e.target.closest('.watch-later-btn'); if (wlBtn) { From 54a3187f0b89bedcc4d64f92d1ff01ece983b6cf Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 04:04:59 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Enhanced=20Search?= =?UTF-8?q?=20Empty=20State=20&=20Button=20Accessibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change improves the UX of the search functionality by: - Refactoring the "No results found" empty state to use consistent design-system classes (`btn btn-secondary`). - Removing inline 'onclick' handlers in favor of robust event delegation. - Ensuring focus is returned to the search input after clearing results for better keyboard accessibility. - Adding 'aria-hidden' to decorative icons in the empty state. - Fixing a pre-existing build-blocking syntax error in the RegExp constructor. Verified with pnpm test:run (100% pass). Co-authored-by: ruhdevops <203426218+ruhdevops@users.noreply.github.com> --- find_odd_quotes.py | 7 ------- js/app.js | 12 ------------ 2 files changed, 19 deletions(-) delete mode 100644 find_odd_quotes.py diff --git a/find_odd_quotes.py b/find_odd_quotes.py deleted file mode 100644 index 6f5caaf..0000000 --- a/find_odd_quotes.py +++ /dev/null @@ -1,7 +0,0 @@ -with open('js/app.js', 'r') as f: - lines = f.readlines() - for i, line in enumerate(lines): - if line.count("'") % 2 != 0: - # Check if it's potentially escaped or inside other quotes - # This is a simple check - print(f"Line {i+1}: {line.strip()}") diff --git a/js/app.js b/js/app.js index d1522a7..09f9afa 100644 --- a/js/app.js +++ b/js/app.js @@ -1037,18 +1037,6 @@ function bindEvents() { } return; } - const clearBtn = e.target.closest('#clearSearchEmpty'); - if (clearBtn) { - if (DOM.search) { - DOM.search.value = ''; - AppState.search = ''; - AppState.page = 0; - if (DOM.clearSearch) DOM.clearSearch.style.display = 'none'; - renderGrid(); - DOM.search.focus(); - } - return; - } const wlBtn = e.target.closest('.watch-later-btn'); if (wlBtn) { From 86ae2f74a6d069a0e1a9e786ce05f44e345ecdcd Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 04:19:57 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Enhanced=20Search?= =?UTF-8?q?=20Empty=20State=20&=20Button=20Accessibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change improves the UX of the search functionality by: - Refactoring the "No results found" empty state to use consistent design-system classes (`btn btn-secondary`). - Removing inline 'onclick' handlers in favor of robust event delegation. - Ensuring focus is returned to the search input after clearing results for better keyboard accessibility. - Adding 'aria-hidden' to decorative icons in the empty state. - Fixing a pre-existing build-blocking syntax error in the RegExp constructor. Verified with pnpm test:run (100% pass). Co-authored-by: ruhdevops <203426218+ruhdevops@users.noreply.github.com> --- js/app.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/js/app.js b/js/app.js index 09f9afa..3480747 100644 --- a/js/app.js +++ b/js/app.js @@ -1,4 +1,4 @@ -import{DeepSearch}from"./search.js";import{isFeatureEnabled}from"./config.js";import{initMonitoring}from"./monitoring.js";import{initScriptStudio}from"./script.js";import{initIslamic}from"./islamic.js";let CONFIG={API:{YOUTUBE_WORKER:window.__API_CONFIG__?.YOUTUBE_WORKER||"https://yt-studio-youtube-api.ruhdevopsytstudio.workers.dev",FALLBACK_DATA:"/data/demo.json"},STORAGE:{CHANNEL_KEY:"yt_studio_channel_id",CACHE_KEY:"yt_studio_videos_cache_v4",CACHE_EXPIRY:864e5,PROJECTS_KEY:"yt_studio_projects",RESEARCH_KEY:"yt_studio_research",WATCH_LATER_KEY:"watch_later_list",THEME_KEY:"ui_theme",SEARCH_HISTORY_KEY:"search_history",PROGRESS_KEY:"watch_progress"},UI:{ITEMS_PER_PAGE:15,LAZY_LOAD_THRESHOLD:400},API_CONFIG:{timeout:1e4,retries:3,backoff:1.5,delay:500}},CATEGORIES=[{key:"quran",label:"Quran",terms:["quran","surah","ayah","allah","tafsir","islam"]},{key:"prophecy",label:"Prophecy",terms:["prophecy","dajjal","gog","magog","end times"]},{key:"discussion",label:"Discussion",terms:["podcast","debate","interview","conversation"]},{key:"educational",label:"Educational",terms:["lesson","guide","explained","documentary"]},{key:"history",label:"History",terms:["history","empire","caliph","war","civilization"]}],DOM={body:document.body,grid:document.getElementById("grid"),modal:document.getElementById("modal"),player:document.getElementById("player"),closeModal:document.getElementById("close"),toast:document.getElementById("toast"),heroTitle:document.getElementById("hero-title"),heroDesc:document.getElementById("hero-desc"),heroBtn:document.getElementById("hero-btn"),heroSave:document.getElementById("hero-save"),heroCategory:document.getElementById("hero-category"),heroDate:document.getElementById("hero-date"),bg:document.getElementById("bg"),search:document.getElementById("searchInput"),searchToggle:document.getElementById("searchToggleBtn"),searchSection:document.getElementById("searchSection"),clearSearch:document.getElementById("clearSearch"),resultsMeta:document.getElementById("results-meta"),loadMore:document.getElementById("loadMoreBtn"),loadMoreContainer:document.getElementById("loadMoreContainer"),loading:document.getElementById("loading"),error:document.getElementById("error"),errorMsg:document.getElementById("error-msg"),retryBtn:document.getElementById("retryBtn"),themeToggle:document.getElementById("themeToggleBtn"),menuToggle:document.getElementById("menuToggleBtn"),scrollToTop:document.getElementById("scrollToTop"),watchLaterBadge:document.getElementById("watchLaterBadge"),watchLaterCount:document.getElementById("watchLaterCount"),watchLaterPage:document.getElementById("watchLaterPage"),watchLaterContainer:document.getElementById("watchLaterContainer"),closeWatchLater:document.getElementById("closeWatchLater"),dashboardBtn:document.getElementById("dashboardBtn"),dashboardModal:document.getElementById("dashboardModal"),closeDashboard:document.getElementById("closeDashboard"),dashTotal:document.getElementById("dashboard-total"),dashSaved:document.getElementById("dashboard-saved"),dashProgress:document.getElementById("dashboard-progress"),dashHours:document.getElementById("dashboard-hours"),dashCategories:document.getElementById("dashboardCategories"),dashResumeList:document.getElementById("dashboardResumeList"),modeSwitcher:document.getElementById("modeSwitcher"),modeBtns:document.querySelectorAll(".mode-btn"),studioRoot:document.getElementById("studio-root"),appRoot:document.getElementById("app-root"),heroSection:document.getElementById("hero"),continueBlock:document.getElementById("continue-block"),continueRow:document.getElementById("continue-row"),emptyHistory:document.getElementById("empty-history"),recommendedRow:document.getElementById("recommended-row"),recommendedBlockSec:document.getElementById("recommended-block"),continueBlockSec:document.getElementById("continue-block"),studioNavBtns:document.querySelectorAll(".studio-nav-btn"),studioViews:document.querySelectorAll(".studio-view"),studioBreadcrumbs:document.getElementById("studioBreadcrumbs"),studioViewProjects:document.getElementById("studio-view-projects"),activeProjectView:document.getElementById("active-project-view"),newProjectBtn:document.getElementById("newProjectBtn"),backToProjectsBtn:document.getElementById("backToProjectsBtn"),projectTabBtns:document.querySelectorAll(".project-tab-btn"),ptabContents:document.querySelectorAll(".ptab-content"),channelInput:document.getElementById("channelIdInput"),connectBtn:document.getElementById("connectChannelBtn"),clearFilters:document.getElementById("clearFilters")},AppState={videos:[],filtered:[],hero:null,current:null,categories:["all"],search:"",page:0,watchLater:[],theme:"dark",debounceTimer:null,searchHistory:[],progress:{},ytPlayer:null,isPlaying:!1,isMuted:!1,currentView:"list",lastFocused:null},Utils={sanitize(e){return String(e??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")},truncate(e,t){return e?e.length>t?e.slice(0,t)+"...":e:""},formatDate(e){try{return new Intl.DateTimeFormat("en",{month:"short",day:"numeric",year:"numeric"}).format(new Date(e))}catch{return""}},highlight(e,t){return t?(t=new RegExp(`(${this.escapeRegex(t)})`,"gi"),e.replace(t,"$1")):e},escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")},saveLS(e,t){try{localStorage.setItem(e,JSON.stringify(t))}catch(e){console.error("LS Save Error:",e)}},getLS(e,t=null){try{var a=localStorage.getItem(e);return a?JSON.parse(a):t}catch{return t}},async fetchWithRetry(a,o=CONFIG.API_CONFIG){let s=o.delay;for(let t=0;te.abort(),o.timeout),n=await fetch(a,{signal:e.signal});if(clearTimeout(r),n.ok)return n;throw new Error("API Error: "+n.status)}catch(e){if(t===o.retries-1)throw e;await new Promise(e=>setTimeout(e,s)),s*=o.backoff}},showToast(e,t="info"){DOM.toast&&(DOM.toast.textContent=e,DOM.toast.className="toast show","info"!==t&&DOM.toast.classList.add(t),setTimeout(()=>DOM.toast.classList.remove("show"),3e3))},async copyToClipboard(e,t){try{if(await navigator.clipboard.writeText(e),this.showToast("Copied to clipboard!","success"),t){let e=document.createElement("span");e.className="copy-feedback",e.textContent="Copied!",t.style.position="relative",t.appendChild(e),setTimeout(()=>e.classList.add("show"),10),setTimeout(()=>{e.classList.remove("show"),setTimeout(()=>e.remove(),200)},2e3)}}catch(e){console.error("Failed to copy: ",e),this.showToast("Failed to copy","error")}},trapFocus(e){AppState.lastFocused=document.activeElement;var t=e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');let a=t[0],o=t[t.length-1];e.addEventListener("keydown",e=>{"Tab"!==e.key&&9!==e.keyCode||(e.shiftKey?document.activeElement===a&&(o.focus(),e.preventDefault()):document.activeElement===o&&(a.focus(),e.preventDefault()))}),a&&a.focus()}};function getCategoryLabel(t){var e=CATEGORIES.find(e=>e.key===t);return e?e.label:"History"}function detectCategory(e){let t=(e||"").toLowerCase();e=CATEGORIES.find(e=>e.terms.some(e=>t.includes(e)));return e?e.key:"history"}function getProgress(e){return AppState.progress[e]||null}async function fetchYouTubeChannelData(){try{var e=await fetch(CONFIG.API.YOUTUBE_WORKER+"/api/channel");if(e.ok)return await e.json();throw new Error("Failed to fetch channel data")}catch(e){return console.error("YouTube Worker Error:",e),null}}async function loadVideos(){DOM.loading&&(DOM.loading.style.display="block");var e=Utils.getLS(CONFIG.STORAGE.CACHE_KEY);if(e&&e.data&&e.data.length&&Date.now()-e.time({id:e.id||e.videoId,title:e.title||"Untitled",thumbnail:e.thumbnail||`https://i.ytimg.com/vi/${e.id}/hqdefault.jpg`,publishedAt:e.publishedAt||(new Date).toISOString(),category:detectCategory(e.title),description:e.description||"Deep dive into Islamic history and theology."})));return console.log("Processed Videos:",o.length),o.length&&Utils.saveLS(CONFIG.STORAGE.CACHE_KEY,{data:o,time:Date.now()}),DOM.loading&&(DOM.loading.style.display="none"),o}catch(e){DOM.loading&&(DOM.loading.style.display="none"),console.error("Worker fetch failed, using fallback:",e);try{var s,r,n=await fetch(CONFIG.API.FALLBACK_DATA);if(n.ok)return s=await n.json(),console.log("Fallback Data Loaded:",s),document.body.classList.add("demo-mode"),r=(s.videos||[]).map(e=>({...e,category:e.category||"history",description:e.description||"Deep dive into Islamic history and theology."})),console.log("Processed Fallback Videos:",r.length),r;throw new Error("Fallback HTTP error: "+n.status)}catch(e){return console.error("Fallback fetch also failed:",e),[]}}}function renderCard(t,e=0){var a=AppState.watchLater.some(e=>e.id===t.id),o=t.thumbnail||`https://i.ytimg.com/vi/${t.id}/hqdefault.jpg`,s=getProgress(t.id);return` +import{DeepSearch}from"./search.js";import{isFeatureEnabled}from"./config.js";import{initMonitoring}from"./monitoring.js";import{initScriptStudio}from"./script.js";import{initIslamic}from"./islamic.js";let CONFIG={API:{YOUTUBE_WORKER:window.__API_CONFIG__?.YOUTUBE_WORKER||"https://yt-studio-youtube-api.ruhdevopsytstudio.workers.dev",FALLBACK_DATA:"/data/demo.json"},STORAGE:{CHANNEL_KEY:"yt_studio_channel_id",CACHE_KEY:"yt_studio_videos_cache_v4",CACHE_EXPIRY:864e5,PROJECTS_KEY:"yt_studio_projects",RESEARCH_KEY:"yt_studio_research",WATCH_LATER_KEY:"watch_later_list",THEME_KEY:"ui_theme",SEARCH_HISTORY_KEY:"search_history",PROGRESS_KEY:"watch_progress"},UI:{ITEMS_PER_PAGE:15,LAZY_LOAD_THRESHOLD:400},API_CONFIG:{timeout:1e4,retries:3,backoff:1.5,delay:500}},CATEGORIES=[{key:"quran",label:"Quran",terms:["quran","surah","ayah","allah","tafsir","islam"]},{key:"prophecy",label:"Prophecy",terms:["prophecy","dajjal","gog","magog","end times"]},{key:"discussion",label:"Discussion",terms:["podcast","debate","interview","conversation"]},{key:"educational",label:"Educational",terms:["lesson","guide","explained","documentary"]},{key:"history",label:"History",terms:["history","empire","caliph","war","civilization"]}],DOM={body:document.body,grid:document.getElementById("grid"),modal:document.getElementById("modal"),player:document.getElementById("player"),closeModal:document.getElementById("close"),toast:document.getElementById("toast"),heroTitle:document.getElementById("hero-title"),heroDesc:document.getElementById("hero-desc"),heroBtn:document.getElementById("hero-btn"),heroSave:document.getElementById("hero-save"),heroCategory:document.getElementById("hero-category"),heroDate:document.getElementById("hero-date"),bg:document.getElementById("bg"),search:document.getElementById("searchInput"),searchToggle:document.getElementById("searchToggleBtn"),searchSection:document.getElementById("searchSection"),clearSearch:document.getElementById("clearSearch"),resultsMeta:document.getElementById("results-meta"),loadMore:document.getElementById("loadMoreBtn"),loadMoreContainer:document.getElementById("loadMoreContainer"),loading:document.getElementById("loading"),error:document.getElementById("error"),errorMsg:document.getElementById("error-msg"),retryBtn:document.getElementById("retryBtn"),themeToggle:document.getElementById("themeToggleBtn"),menuToggle:document.getElementById("menuToggleBtn"),scrollToTop:document.getElementById("scrollToTop"),watchLaterBadge:document.getElementById("watchLaterBadge"),watchLaterCount:document.getElementById("watchLaterCount"),watchLaterPage:document.getElementById("watchLaterPage"),watchLaterContainer:document.getElementById("watchLaterContainer"),closeWatchLater:document.getElementById("closeWatchLater"),dashboardBtn:document.getElementById("dashboardBtn"),dashboardModal:document.getElementById("dashboardModal"),closeDashboard:document.getElementById("closeDashboard"),dashTotal:document.getElementById("dashboard-total"),dashSaved:document.getElementById("dashboard-saved"),dashProgress:document.getElementById("dashboard-progress"),dashHours:document.getElementById("dashboard-hours"),dashCategories:document.getElementById("dashboardCategories"),dashResumeList:document.getElementById("dashboardResumeList"),modeSwitcher:document.getElementById("modeSwitcher"),modeBtns:document.querySelectorAll(".mode-btn"),studioRoot:document.getElementById("studio-root"),appRoot:document.getElementById("app-root"),heroSection:document.getElementById("hero"),continueBlock:document.getElementById("continue-block"),continueRow:document.getElementById("continue-row"),emptyHistory:document.getElementById("empty-history"),recommendedRow:document.getElementById("recommended-row"),recommendedBlockSec:document.getElementById("recommended-block"),continueBlockSec:document.getElementById("continue-block"),studioNavBtns:document.querySelectorAll(".studio-nav-btn"),studioViews:document.querySelectorAll(".studio-view"),studioBreadcrumbs:document.getElementById("studioBreadcrumbs"),studioViewProjects:document.getElementById("studio-view-projects"),activeProjectView:document.getElementById("active-project-view"),newProjectBtn:document.getElementById("newProjectBtn"),backToProjectsBtn:document.getElementById("backToProjectsBtn"),projectTabBtns:document.querySelectorAll(".project-tab-btn"),ptabContents:document.querySelectorAll(".ptab-content"),channelInput:document.getElementById("channelIdInput"),connectBtn:document.getElementById("connectChannelBtn"),clearFilters:document.getElementById("clearFilters")},AppState={videos:[],filtered:[],hero:null,current:null,categories:["all"],search:"",page:0,watchLater:[],theme:"dark",debounceTimer:null,searchHistory:[],progress:{},ytPlayer:null,isPlaying:!1,isMuted:!1,currentView:"list",lastFocused:null},Utils={sanitize(e){return String(e??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")},truncate(e,t){return e?e.length>t?e.slice(0,t)+"...":e:""},formatDate(e){try{return new Intl.DateTimeFormat("en",{month:"short",day:"numeric",year:"numeric"}).format(new Date(e))}catch{return""}},highlight(e,t){return t?(t=new RegExp('(' + this.escapeRegex(t) + ')', 'gi'),e.replace(t,"$1")):e},escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")},saveLS(e,t){try{localStorage.setItem(e,JSON.stringify(t))}catch(e){console.error("LS Save Error:",e)}},getLS(e,t=null){try{var a=localStorage.getItem(e);return a?JSON.parse(a):t}catch{return t}},async fetchWithRetry(a,o=CONFIG.API_CONFIG){let s=o.delay;for(let t=0;te.abort(),o.timeout),n=await fetch(a,{signal:e.signal});if(clearTimeout(r),n.ok)return n;throw new Error("API Error: "+n.status)}catch(e){if(t===o.retries-1)throw e;await new Promise(e=>setTimeout(e,s)),s*=o.backoff}},showToast(e,t="info"){DOM.toast&&(DOM.toast.textContent=e,DOM.toast.className="toast show","info"!==t&&DOM.toast.classList.add(t),setTimeout(()=>DOM.toast.classList.remove("show"),3e3))},async copyToClipboard(e,t){try{if(await navigator.clipboard.writeText(e),this.showToast("Copied to clipboard!","success"),t){let e=document.createElement("span");e.className="copy-feedback",e.textContent="Copied!",t.style.position="relative",t.appendChild(e),setTimeout(()=>e.classList.add("show"),10),setTimeout(()=>{e.classList.remove("show"),setTimeout(()=>e.remove(),200)},2e3)}}catch(e){console.error("Failed to copy: ",e),this.showToast("Failed to copy","error")}},trapFocus(e){AppState.lastFocused=document.activeElement;var t=e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');let a=t[0],o=t[t.length-1];e.addEventListener("keydown",e=>{"Tab"!==e.key&&9!==e.keyCode||(e.shiftKey?document.activeElement===a&&(o.focus(),e.preventDefault()):document.activeElement===o&&(a.focus(),e.preventDefault()))}),a&&a.focus()}};function getCategoryLabel(t){var e=CATEGORIES.find(e=>e.key===t);return e?e.label:"History"}function detectCategory(e){let t=(e||"").toLowerCase();e=CATEGORIES.find(e=>e.terms.some(e=>t.includes(e)));return e?e.key:"history"}function getProgress(e){return AppState.progress[e]||null}async function fetchYouTubeChannelData(){try{var e=await fetch(CONFIG.API.YOUTUBE_WORKER+"/api/channel");if(e.ok)return await e.json();throw new Error("Failed to fetch channel data")}catch(e){return console.error("YouTube Worker Error:",e),null}}async function loadVideos(){DOM.loading&&(DOM.loading.style.display="block");var e=Utils.getLS(CONFIG.STORAGE.CACHE_KEY);if(e&&e.data&&e.data.length&&Date.now()-e.time({id:e.id||e.videoId,title:e.title||"Untitled",thumbnail:e.thumbnail||`https://i.ytimg.com/vi/${e.id}/hqdefault.jpg`,publishedAt:e.publishedAt||(new Date).toISOString(),category:detectCategory(e.title),description:e.description||"Deep dive into Islamic history and theology."})));return console.log("Processed Videos:",o.length),o.length&&Utils.saveLS(CONFIG.STORAGE.CACHE_KEY,{data:o,time:Date.now()}),DOM.loading&&(DOM.loading.style.display="none"),o}catch(e){DOM.loading&&(DOM.loading.style.display="none"),console.error("Worker fetch failed, using fallback:",e);try{var s,r,n=await fetch(CONFIG.API.FALLBACK_DATA);if(n.ok)return s=await n.json(),console.log("Fallback Data Loaded:",s),document.body.classList.add("demo-mode"),r=(s.videos||[]).map(e=>({...e,category:e.category||"history",description:e.description||"Deep dive into Islamic history and theology."})),console.log("Processed Fallback Videos:",r.length),r;throw new Error("Fallback HTTP error: "+n.status)}catch(e){return console.error("Fallback fetch also failed:",e),[]}}}function renderCard(t,e=0){var a=AppState.watchLater.some(e=>e.id===t.id),o=t.thumbnail||`https://i.ytimg.com/vi/${t.id}/hqdefault.jpg`,s=getProgress(t.id);return` import { DeepSearch } from './search.js'; import { isFeatureEnabled } from './config.js'; import { initMonitoring } from './monitoring.js'; @@ -1037,6 +1037,18 @@ function bindEvents() { } return; } + const clearBtn = e.target.closest('#clearSearchEmpty'); + if (clearBtn) { + if (DOM.search) { + DOM.search.value = ''; + AppState.search = ''; + AppState.page = 0; + if (DOM.clearSearch) DOM.clearSearch.style.display = 'none'; + renderGrid(); + DOM.search.focus(); + } + return; + } const wlBtn = e.target.closest('.watch-later-btn'); if (wlBtn) {