diff --git a/TODO.md b/TODO.md
index 03aaa1a..fce32e2 100644
--- a/TODO.md
+++ b/TODO.md
@@ -511,3 +511,17 @@
- `bash scripts/selftest.sh`
- What is still risky or incomplete:
- Partial-result responses are now preferred over hard 504s, so some searches may return fewer reviewed items when the time budget is exhausted.
+
+- Date: `2026-03-16`
+- What changed:
+ - Loosened Envato and Artgrid query templates by adding unquoted variants so source discovery is less brittle when SearXNG has weak exact-phrase coverage.
+ - Replaced the Google Video modal embed with a dedicated in-app panel using thumbnail + metadata + action buttons, instead of relying on unstable YouTube embed playback.
+ - Bumped frontend asset version to `20260316g`.
+- Why it changed:
+ - The latest log showed Envato and Artgrid repeatedly returning `rawCount: 0`, which is consistent with over-constrained exact-phrase queries.
+ - Google Video still hit YouTube player error `153`, so the modal needed a non-embed fallback that remains usable.
+- How it was verified:
+ - log review from `ai-media-hub-2026-03-16T06-23-45-335Z.log`
+ - `go test ./...`
+- What is still risky or incomplete:
+ - Even with looser queries, SearXNG quality still controls whether Envato and Artgrid candidates are discoverable for a given term.
diff --git a/backend/services/cse.go b/backend/services/cse.go
index 25bcfb9..87737d9 100644
--- a/backend/services/cse.go
+++ b/backend/services/cse.go
@@ -485,21 +485,23 @@ func buildGoogleVideoQueries(base string) []string {
func buildEnvatoQueries(base string) []string {
return []string{
- fmt.Sprintf(`"%s" ("stock footage" OR "stock video" OR "b-roll" OR cinematic) site:elements.envato.com`, base),
- fmt.Sprintf(`"%s" ("stock footage" OR "stock video" OR "b-roll" OR cinematic) site:elements.envato.com/stock-video`, base),
- fmt.Sprintf(`"%s" ("motion graphics" OR "backgrounds" OR "establishing shot" OR "loop") site:elements.envato.com`, base),
- fmt.Sprintf(`"%s" ("urban" OR "night city" OR "cyberpunk" OR "sci-fi") site:elements.envato.com`, base),
+ fmt.Sprintf(`%s ("stock footage" OR "stock video" OR "b-roll" OR cinematic) site:elements.envato.com`, base),
+ fmt.Sprintf(`%s ("stock footage" OR "stock video" OR "b-roll" OR cinematic) site:elements.envato.com/stock-video`, base),
+ fmt.Sprintf(`%s ("motion graphics" OR "backgrounds" OR "establishing shot" OR "loop") site:elements.envato.com`, base),
+ fmt.Sprintf(`%s ("urban" OR "night city" OR "cyberpunk" OR "sci-fi") site:elements.envato.com`, base),
+ fmt.Sprintf(`"%s" site:elements.envato.com`, base),
}
}
func buildArtgridQueries(base string) []string {
return []string{
- fmt.Sprintf(`"%s" ("stock footage" OR "b-roll" OR cinematic OR editorial) site:artgrid.io/clip/`, base),
- fmt.Sprintf(`"%s" ("footage" OR "cinematic" OR "establishing shot") site:artgrid.io/clip/`, base),
- fmt.Sprintf(`"%s" ("stock footage" OR "b-roll" OR cinematic OR editorial) site:artlist.io/stock-footage/clip/`, base),
- fmt.Sprintf(`"%s" ("footage" OR "cinematic" OR "establishing shot") site:artlist.io/stock-footage/clip/`, base),
- fmt.Sprintf(`"%s" ("night drive" OR "urban night" OR "wet road" OR "cyberpunk") site:artgrid.io/clip/`, base),
- fmt.Sprintf(`"%s" ("drone" OR "city skyline" OR "street scene" OR "mood shot") site:artlist.io/stock-footage/clip/`, base),
+ fmt.Sprintf(`%s ("stock footage" OR "b-roll" OR cinematic OR editorial) site:artgrid.io/clip/`, base),
+ fmt.Sprintf(`%s ("footage" OR "cinematic" OR "establishing shot") site:artgrid.io/clip/`, base),
+ fmt.Sprintf(`%s ("stock footage" OR "b-roll" OR cinematic OR editorial) site:artlist.io/stock-footage/clip/`, base),
+ fmt.Sprintf(`%s ("footage" OR "cinematic" OR "establishing shot") site:artlist.io/stock-footage/clip/`, base),
+ fmt.Sprintf(`%s ("night drive" OR "urban night" OR "wet road" OR "cyberpunk") site:artgrid.io/clip/`, base),
+ fmt.Sprintf(`%s ("drone" OR "city skyline" OR "street scene" OR "mood shot") site:artlist.io/stock-footage/clip/`, base),
+ fmt.Sprintf(`"%s" site:artgrid.io/clip/`, base),
}
}
diff --git a/frontend/app.js b/frontend/app.js
index 923a1e7..e4579ca 100644
--- a/frontend/app.js
+++ b/frontend/app.js
@@ -46,6 +46,9 @@ const resultModalFrame = document.getElementById("resultModalFrame");
const resultModalMediaFrame = document.getElementById("resultModalMediaFrame");
const resultModalVideo = document.getElementById("resultModalVideo");
const resultModalThumbnail = document.getElementById("resultModalThumbnail");
+const resultModalGooglePanel = document.getElementById("resultModalGooglePanel");
+const resultModalGoogleImage = document.getElementById("resultModalGoogleImage");
+const resultModalGoogleText = document.getElementById("resultModalGoogleText");
const resultModalOpenExternal = document.getElementById("resultModalOpenExternal");
const resultModalDownload = document.getElementById("resultModalDownload");
const closeResultModal = document.getElementById("closeResultModal");
@@ -59,6 +62,9 @@ const resultModalReady = Boolean(
resultModalMediaFrame &&
resultModalVideo &&
resultModalThumbnail &&
+ resultModalGooglePanel &&
+ resultModalGoogleImage &&
+ resultModalGoogleText &&
resultModalOpenExternal &&
resultModalDownload &&
closeResultModal,
@@ -407,10 +413,13 @@ function resetResultModalMedia() {
resultModalVideo.pause();
detachVideoSource(resultModalVideo);
resultModalThumbnail.removeAttribute("src");
+ resultModalGoogleImage.removeAttribute("src");
+ resultModalGoogleText.textContent = "";
resultModalMediaFrame.style.aspectRatio = "";
setHidden(resultModalFrame, true, "");
setHidden(resultModalVideo, true, "");
setHidden(resultModalThumbnail, true, "");
+ setHidden(resultModalGooglePanel, true, "flex");
}
function showResultModalFrame(src) {
@@ -435,6 +444,13 @@ function showResultModalThumbnail(src, alt) {
setHidden(resultModalThumbnail, false, "");
}
+function showResultModalGooglePanel(item) {
+ resultModalGoogleImage.src = item.thumbnailUrl || PREVIEW_PLACEHOLDER;
+ resultModalGoogleImage.alt = item.title || "";
+ resultModalGoogleText.textContent = item.snippet || item.reason || "YouTube 페이지 열기 또는 Direct Download를 사용할 수 있습니다.";
+ setHidden(resultModalGooglePanel, false, "flex");
+}
+
function renderResults(results) {
searchResults.innerHTML = "";
if (!results.length) {
@@ -512,7 +528,7 @@ function openResultModal(item) {
resultModalDownload.classList.toggle("hidden", !canDirectDownload);
resetResultModalMedia();
if (item.source === "Google Video") {
- showResultModalFrame(buildResultModalEmbedURL(item));
+ showResultModalGooglePanel(item);
} else {
showResultModalFrame(item.link || "about:blank");
}
diff --git a/frontend/index.html b/frontend/index.html
index 954d6b4..509c9e0 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -166,6 +166,17 @@
+
Google Video
+ +