musaw
fix(ui): prevent card overflow and improve task labeling for v1.1.1
3614ee2
---
layout: null
title: Pashto Resource Search
description: Search verified Pashto (Pukhto/Pashto) datasets, models, tools, benchmarks, papers, and projects.
permalink: /search/
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pashto Resource Search</title>
<meta name="description" content="Search verified Pashto (Pukhto/Pashto) resources for Automatic Speech Recognition (ASR), Text-to-Speech (TTS), Natural Language Processing (NLP), machine translation, tools, datasets, models, and benchmarks.">
<meta name="keywords" content="Pashto resources, Pukhto resources, Pashto datasets, Pashto ASR, Pashto TTS, Pashto NLP, Pashto machine translation, Pashto benchmarks">
<meta name="robots" content="index,follow,max-image-preview:large">
<link rel="canonical" href="https://musawer1214.github.io/pashto-language-resources/search/">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Pashto Language Resources Hub">
<meta property="og:title" content="Pashto Resource Search">
<meta property="og:description" content="Discover verified Pashto datasets, models, tools, benchmarks, projects, and papers.">
<meta property="og:url" content="https://musawer1214.github.io/pashto-language-resources/search/">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Pashto Resource Search">
<meta name="twitter:description" content="Search and filter verified Pashto language technology resources.">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"name": "Pashto Resource Search",
"url": "https://musawer1214.github.io/pashto-language-resources/search/",
"description": "Search verified and candidate Pashto resources for Automatic Speech Recognition (ASR), Text-to-Speech (TTS), Natural Language Processing (NLP), machine translation, tools, datasets, and benchmarks.",
"inLanguage": "en",
"about": [
"Pashto datasets",
"Pashto ASR",
"Pashto TTS",
"Pashto NLP",
"Pashto machine translation"
]
}
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Noto+Naskh+Arabic:wght@500;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #f3f2ea;
--panel: #fffdf7;
--ink: #1d2628;
--muted: #55656a;
--line: #d5ddd8;
--brand: #0f6f61;
--brand-soft: #dcf2ee;
--accent: #b9621f;
--accent-soft: #f8e6d7;
--shadow: 0 14px 36px rgba(16, 29, 35, 0.12);
--radius-lg: 18px;
--radius-md: 12px;
--radius-sm: 9px;
}
* { box-sizing: border-box; }
body {
margin: 0;
color: var(--ink);
font-family: "Manrope", "Segoe UI", sans-serif;
min-height: 100vh;
background:
radial-gradient(circle at 8% 8%, #ffe0bf 0, rgba(255, 224, 191, 0) 30%),
radial-gradient(circle at 92% 7%, #d2ece4 0, rgba(210, 236, 228, 0) 35%),
var(--bg);
}
a { color: var(--brand); }
.container {
max-width: 1180px;
margin: 0 auto;
padding: 22px 18px 42px;
}
.hero {
background: linear-gradient(130deg, #fffef9, #f5faf8);
border: 1px solid var(--line);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
padding: 20px;
margin-bottom: 14px;
}
.hero-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 14px;
flex-wrap: wrap;
}
.eyebrow {
margin: 0 0 8px;
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent);
font-weight: 800;
}
h1 {
margin: 0;
font-size: 34px;
line-height: 1.08;
letter-spacing: -0.02em;
}
.subtitle {
margin: 10px 0 0;
color: var(--muted);
line-height: 1.55;
max-width: 760px;
}
.stat-row {
display: grid;
grid-template-columns: repeat(3, minmax(120px, 1fr));
gap: 8px;
width: min(380px, 100%);
}
.stat {
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: var(--panel);
padding: 10px;
text-align: center;
}
.stat strong {
display: block;
font-size: 20px;
line-height: 1;
color: var(--brand);
}
.stat span {
display: block;
margin-top: 4px;
color: var(--muted);
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.quick-links {
margin-top: 14px;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.pill-link {
border: 1px solid #b8cec6;
border-radius: 999px;
background: #f8fcfa;
color: var(--brand);
text-decoration: none;
font-size: 13px;
font-weight: 700;
padding: 7px 12px;
}
.toolbar {
margin-bottom: 12px;
display: grid;
gap: 12px;
}
.panel {
border: 1px solid var(--line);
border-radius: var(--radius-lg);
background: var(--panel);
box-shadow: var(--shadow);
padding: 16px;
}
.controls {
display: grid;
grid-template-columns: 2fr repeat(5, minmax(140px, 1fr));
gap: 10px;
}
.field {
display: flex;
flex-direction: column;
gap: 5px;
}
.field label {
font-size: 11px;
letter-spacing: 0.05em;
text-transform: uppercase;
font-weight: 800;
color: var(--muted);
}
input, select {
width: 100%;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
background: #fff;
color: var(--ink);
padding: 10px;
font: inherit;
}
input:focus, select:focus, button:focus {
outline: 2px solid #7bc7b8;
outline-offset: 1px;
border-color: #7bc7b8;
}
.action-row {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 10px;
align-items: center;
}
.group {
display: flex;
flex-wrap: wrap;
gap: 7px;
align-items: center;
}
.btn {
border: 1px solid var(--line);
background: #fff;
color: var(--ink);
border-radius: 999px;
padding: 7px 12px;
font-size: 13px;
font-weight: 700;
cursor: pointer;
}
.btn:hover { border-color: #98b9ae; }
.btn.brand {
background: var(--brand);
color: #fff;
border-color: var(--brand);
}
.btn.light {
background: #f6faf8;
color: var(--brand);
border-color: #b9d8cf;
}
.btn.active {
background: var(--accent-soft);
border-color: #efc08f;
color: #915117;
}
.intent-row {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 7px;
}
.intent {
border: 1px solid #cfdad4;
background: #fbfdfc;
border-radius: 999px;
padding: 6px 11px;
font-size: 13px;
font-weight: 700;
cursor: pointer;
color: var(--muted);
}
.intent:hover { border-color: #9cb8af; color: var(--ink); }
.summary {
margin: 0 2px 12px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
color: var(--muted);
font-size: 14px;
}
.badge {
background: var(--brand-soft);
color: var(--brand);
border: 1px solid #b7dccc;
padding: 4px 9px;
border-radius: 999px;
font-weight: 700;
font-size: 12px;
}
.results {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 12px;
}
.results.grid { grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); }
.results.list { grid-template-columns: 1fr; }
.card {
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: var(--panel);
box-shadow: var(--shadow);
padding: 13px;
display: flex;
flex-direction: column;
gap: 9px;
animation: cardIn 360ms ease;
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.chip {
border-radius: 999px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
padding: 3px 8px;
border: 1px solid transparent;
}
.chip.category { background: var(--brand-soft); color: var(--brand); border-color: #b7dccc; }
.chip.source { background: var(--accent-soft); color: #915117; border-color: #efc89f; }
.chip.status { background: #edf2ff; color: #3f4f86; border-color: #d0dbf6; }
.title {
margin: 0;
font-size: 17px;
line-height: 1.3;
overflow-wrap: anywhere;
word-break: break-word;
}
.title a {
color: var(--ink);
text-decoration: none;
border-bottom: 1px solid transparent;
display: inline-block;
max-width: 100%;
overflow-wrap: anywhere;
word-break: break-word;
}
.title a:hover { border-bottom-color: currentColor; }
.text {
margin: 0;
color: var(--muted);
line-height: 1.48;
font-size: 13px;
overflow-wrap: anywhere;
word-break: break-word;
}
.meta-links {
margin-top: auto;
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.mini-link {
font-size: 12px;
font-weight: 700;
color: var(--brand);
text-decoration: none;
display: inline-block;
max-width: 100%;
overflow-wrap: anywhere;
word-break: break-word;
}
.tasks {
font-size: 12px;
color: var(--muted);
margin: 0;
overflow-wrap: anywhere;
word-break: break-word;
}
.empty {
border: 1px dashed #b9c4bd;
border-radius: var(--radius-md);
padding: 26px;
background: #fcfcfa;
color: var(--muted);
text-align: center;
}
.crawl {
margin: 0 2px 12px;
color: var(--muted);
line-height: 1.5;
font-size: 14px;
}
.crawl ul {
margin: 8px 0 0;
padding-left: 18px;
}
@keyframes cardIn {
from { opacity: 0; transform: translateY(5px); }
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 1020px) {
.controls { grid-template-columns: repeat(3, minmax(140px, 1fr)); }
.controls .field.search { grid-column: span 3; }
}
@media (max-width: 720px) {
h1 { font-size: 30px; }
.controls { grid-template-columns: 1fr 1fr; }
.controls .field.search { grid-column: span 2; }
.summary { flex-direction: column; align-items: flex-start; }
.stat-row { width: 100%; }
}
@media (max-width: 520px) {
.controls { grid-template-columns: 1fr; }
.controls .field.search { grid-column: span 1; }
h1 { font-size: 28px; }
}
</style>
</head>
<body>
<main class="container">
<header class="hero">
<div class="hero-top">
<div>
<p class="eyebrow">Pukhto / Pashto Resources</p>
<h1>Pashto Technology Resource Search</h1>
<p class="subtitle">
Filter verified and candidate resources for Automatic Speech Recognition (ASR), Text-to-Speech (TTS), Natural Language Processing (NLP), translation, tools, and benchmark work.
Share search states with URL parameters and jump quickly to high-intent pages.
</p>
</div>
<div class="stat-row" aria-label="Catalog metrics">
<div class="stat"><strong id="statTotal">-</strong><span>Total</span></div>
<div class="stat"><strong id="statVerified">-</strong><span>Verified</span></div>
<div class="stat"><strong id="statTasks">-</strong><span>Task Tags</span></div>
</div>
</div>
<nav class="quick-links" aria-label="Quick links">
<a class="pill-link" href="../">Docs Home</a>
<a class="pill-link" href="../pashto_datasets.html">Datasets Page</a>
<a class="pill-link" href="../pashto_asr.html">ASR Page</a>
<a class="pill-link" href="../pashto_tts.html">TTS Page</a>
<a class="pill-link" href="https://github.com/Musawer1214/pashto-language-resources">GitHub</a>
<a class="pill-link" href="https://huggingface.co/Musawer14/pashto-language-resources">Hugging Face</a>
</nav>
</header>
<section class="toolbar panel" aria-label="Search controls">
<div class="controls">
<div class="field search">
<label for="q">Search</label>
<input id="q" type="search" placeholder="Try: Automatic Speech Recognition, PBT_Arab, Translation, Speech, Benchmark" autocomplete="off">
</div>
<div class="field">
<label for="category">Category</label>
<select id="category"></select>
</div>
<div class="field">
<label for="source">Source</label>
<select id="source"></select>
</div>
<div class="field">
<label for="task">Task</label>
<select id="task"></select>
</div>
<div class="field">
<label for="status">Status</label>
<select id="status"></select>
</div>
<div class="field">
<label for="sort">Sort</label>
<select id="sort">
<option value="relevance">Relevance</option>
<option value="title_asc">Title (A-Z)</option>
<option value="title_desc">Title (Z-A)</option>
<option value="source_asc">Source (A-Z)</option>
</select>
</div>
</div>
<div class="action-row">
<div class="group" role="group" aria-label="Result view mode">
<button type="button" class="btn active" id="viewGrid">Grid View</button>
<button type="button" class="btn" id="viewList">List View</button>
</div>
<div class="group">
<button type="button" class="btn light" id="clearFilters">Clear Filters</button>
<button type="button" class="btn brand" id="copyLink">Copy Search Link</button>
</div>
</div>
<div class="intent-row" aria-label="Quick intent filters">
<button type="button" class="intent" data-intent="datasets">Datasets</button>
<button type="button" class="intent" data-intent="asr">ASR</button>
<button type="button" class="intent" data-intent="tts">TTS</button>
<button type="button" class="intent" data-intent="nlp">NLP</button>
<button type="button" class="intent" data-intent="mt">Translation</button>
<button type="button" class="intent" data-intent="verified">Verified Only</button>
</div>
</section>
<div class="summary" aria-live="polite">
<span id="countText">Loading resources...</span>
<span class="badge" id="generatedAt">Catalog timestamp: -</span>
</div>
<section class="crawl" aria-label="Resource categories and crawlable links">
Browse Pashto resource categories directly:
<ul>
<li><a href="https://github.com/Musawer1214/pashto-language-resources/blob/main/resources/datasets/README.md">Pashto datasets</a></li>
<li><a href="https://github.com/Musawer1214/pashto-language-resources/blob/main/resources/models/README.md">Pashto models</a></li>
<li><a href="https://github.com/Musawer1214/pashto-language-resources/blob/main/resources/benchmarks/README.md">Pashto benchmarks</a></li>
<li><a href="https://github.com/Musawer1214/pashto-language-resources/blob/main/resources/tools/README.md">Pashto tools</a></li>
<li><a href="https://github.com/Musawer1214/pashto-language-resources/blob/main/resources/papers/README.md">Pashto papers</a></li>
</ul>
</section>
<noscript>
JavaScript is needed for filtering. Category links above remain fully accessible.
</noscript>
<ul id="results" class="results grid"></ul>
</main>
<script>
const state = {
all: [],
filtered: [],
view: "grid"
};
const els = {
q: document.getElementById("q"),
category: document.getElementById("category"),
source: document.getElementById("source"),
task: document.getElementById("task"),
status: document.getElementById("status"),
sort: document.getElementById("sort"),
results: document.getElementById("results"),
countText: document.getElementById("countText"),
generatedAt: document.getElementById("generatedAt"),
statTotal: document.getElementById("statTotal"),
statVerified: document.getElementById("statVerified"),
statTasks: document.getElementById("statTasks"),
viewGrid: document.getElementById("viewGrid"),
viewList: document.getElementById("viewList"),
clearFilters: document.getElementById("clearFilters"),
copyLink: document.getElementById("copyLink"),
intents: [...document.querySelectorAll(".intent")]
};
const TASK_LABELS = {
"asr": "Automatic Speech Recognition (ASR)",
"tts": "Text-to-Speech (TTS)",
"nlp": "Natural Language Processing (NLP)",
"mt": "Machine Translation (MT)",
"llm": "Large Language Model (LLM)",
"ocr": "Optical Character Recognition (OCR)",
"pos-tagging": "Part-of-Speech Tagging",
"benchmarking": "Benchmarking",
"classification": "Classification",
"demo": "Demo",
"dictionary": "Dictionary",
"research": "Research",
"sentiment": "Sentiment Analysis",
"tooling": "Tooling",
"translation": "Translation"
};
const CATEGORY_LABELS = {
"dataset": "Dataset",
"model": "Model",
"benchmark": "Benchmark",
"tool": "Tool",
"paper": "Paper",
"project": "Project",
"code": "Code"
};
const SOURCE_LABELS = {
"huggingface": "Hugging Face",
"mozilla": "Mozilla",
"kaggle": "Kaggle",
"github": "GitHub",
"gitlab": "GitLab",
"arxiv": "arXiv",
"openalex": "OpenAlex",
"crossref": "Crossref",
"zenodo": "Zenodo",
"dataverse": "Dataverse",
"datacite": "DataCite",
"meta": "Meta",
"other": "Other"
};
const STATUS_LABELS = {
"verified": "Verified",
"candidate": "Candidate"
};
function toTitleCase(value) {
return String(value || "")
.replace(/[_-]+/g, " ")
.split(/\s+/)
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
.join(" ");
}
function replaceAbbreviation(text, shortForm, expanded) {
const fullPhrase = expanded.replace(/\s*\([^)]*\)\s*$/, "");
if (new RegExp(fullPhrase, "i").test(text)) {
return text;
}
return text.replace(new RegExp(`\\b${shortForm}\\b`, "gi"), expanded);
}
function formatDisplayText(value) {
let text = String(value || "").trim();
if (!text) return text;
text = text
.replace(/\bhuggingface\b/gi, "Hugging Face")
.replace(/\bgithub\b/gi, "GitHub")
.replace(/\bgitlab\b/gi, "GitLab")
.replace(/\bopenalex\b/gi, "OpenAlex")
.replace(/\barxiv\b/gi, "arXiv");
text = replaceAbbreviation(text, "ASR", "Automatic Speech Recognition (ASR)");
text = replaceAbbreviation(text, "TTS", "Text-to-Speech (TTS)");
text = replaceAbbreviation(text, "NLP", "Natural Language Processing (NLP)");
text = replaceAbbreviation(text, "MT", "Machine Translation (MT)");
text = replaceAbbreviation(text, "LLM", "Large Language Model (LLM)");
text = replaceAbbreviation(text, "OCR", "Optical Character Recognition (OCR)");
return text.charAt(0).toUpperCase() + text.slice(1);
}
function formatTaskLabel(value) {
const key = String(value || "").toLowerCase();
return TASK_LABELS[key] || toTitleCase(value);
}
function formatCategoryLabel(value) {
const key = String(value || "").toLowerCase();
return CATEGORY_LABELS[key] || toTitleCase(value);
}
function formatSourceLabel(value) {
const key = String(value || "").toLowerCase();
return SOURCE_LABELS[key] || toTitleCase(value);
}
function formatStatusLabel(value) {
const key = String(value || "").toLowerCase();
return STATUS_LABELS[key] || toTitleCase(value);
}
function esc(value) {
return String(value || "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function uniqSorted(values) {
return [...new Set(values.filter(Boolean))].sort((a, b) => a.localeCompare(b));
}
function fillSelect(select, options, allLabel, labelFormatter = toTitleCase) {
select.innerHTML = "";
const allOption = document.createElement("option");
allOption.value = "";
allOption.textContent = allLabel;
select.appendChild(allOption);
for (const opt of options) {
const entry = document.createElement("option");
entry.value = opt;
entry.textContent = labelFormatter(opt);
select.appendChild(entry);
}
}
function updateMetrics() {
const verified = state.all.filter((r) => r.status === "verified").length;
const tasks = uniqSorted(state.all.flatMap((r) => r.tasks || [])).length;
els.statTotal.textContent = String(state.all.length);
els.statVerified.textContent = String(verified);
els.statTasks.textContent = String(tasks);
}
function setView(view) {
state.view = view === "list" ? "list" : "grid";
els.results.classList.toggle("grid", state.view === "grid");
els.results.classList.toggle("list", state.view === "list");
els.viewGrid.classList.toggle("active", state.view === "grid");
els.viewList.classList.toggle("active", state.view === "list");
syncUrl();
}
function matchesQuery(resource, query) {
if (!query) return true;
const hay = [
resource.title,
resource.summary,
resource.primary_use,
resource.category,
resource.source,
resource.status,
...(resource.tags || []),
...(resource.tasks || []),
...(resource.markers || []),
resource.evidence_text
].join(" ").toLowerCase();
return hay.includes(query);
}
function sortItems(items) {
const mode = els.sort.value;
const sorted = [...items];
if (mode === "title_asc") {
sorted.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
} else if (mode === "title_desc") {
sorted.sort((a, b) => (b.title || "").localeCompare(a.title || ""));
} else if (mode === "source_asc") {
sorted.sort((a, b) => (a.source || "").localeCompare(b.source || "") || (a.title || "").localeCompare(b.title || ""));
} else {
sorted.sort((a, b) => {
const av = a.status === "verified" ? 0 : 1;
const bv = b.status === "verified" ? 0 : 1;
if (av !== bv) return av - bv;
return (a.title || "").localeCompare(b.title || "");
});
}
return sorted;
}
function applyFilters() {
const q = els.q.value.trim().toLowerCase();
const category = els.category.value;
const source = els.source.value;
const task = els.task.value;
const status = els.status.value;
state.filtered = sortItems(state.all.filter((resource) => {
if (!matchesQuery(resource, q)) return false;
if (category && resource.category !== category) return false;
if (source && resource.source !== source) return false;
if (status && resource.status !== status) return false;
if (task && !(resource.tasks || []).includes(task)) return false;
return true;
}));
renderResults();
syncUrl();
}
function chip(label, cls) {
return `<span class="chip ${cls}">${esc(label)}</span>`;
}
function renderResults() {
const items = state.filtered;
els.countText.textContent = `${items.length} result${items.length === 1 ? "" : "s"} of ${state.all.length}`;
if (!items.length) {
els.results.innerHTML = '<li class="empty">No Matches. Try Broadening Filters or Clearing One Filter at a Time.</li>';
return;
}
els.results.innerHTML = items.map((resource) => {
const title = esc(resource.title || "Untitled resource");
const url = esc(resource.url || "#");
const summary = esc(formatDisplayText(resource.summary || "No summary provided."));
const primaryUse = esc(formatDisplayText(resource.primary_use || "Not available"));
const evidenceText = esc(formatDisplayText(resource.evidence_text || "Evidence link"));
const evidenceUrl = esc(resource.evidence_url || resource.url || "#");
const tasks = (resource.tasks || []).map((t) => esc(formatTaskLabel(t))).join(", ") || "Not available";
return `
<li class="card">
<div class="chips">
${chip(formatCategoryLabel(resource.category || "unknown"), "category")}
${chip(formatSourceLabel(resource.source || "unknown"), "source")}
${chip(formatStatusLabel(resource.status || "unknown"), "status")}
</div>
<h2 class="title"><a href="${url}" target="_blank" rel="noreferrer">${title}</a></h2>
<p class="text">${summary}</p>
<p class="text"><strong>Primary Use:</strong> ${primaryUse}</p>
<p class="tasks"><strong>Tasks:</strong> ${tasks}</p>
<div class="meta-links">
<a class="mini-link" href="${url}" target="_blank" rel="noreferrer">Open Resource</a>
<a class="mini-link" href="${evidenceUrl}" target="_blank" rel="noreferrer">Pashto Evidence: ${evidenceText}</a>
</div>
</li>
`;
}).join("");
}
function syncUrl() {
const params = new URLSearchParams();
if (els.q.value.trim()) params.set("q", els.q.value.trim());
if (els.category.value) params.set("category", els.category.value);
if (els.source.value) params.set("source", els.source.value);
if (els.task.value) params.set("task", els.task.value);
if (els.status.value) params.set("status", els.status.value);
if (els.sort.value && els.sort.value !== "relevance") params.set("sort", els.sort.value);
if (state.view !== "grid") params.set("view", state.view);
const next = `${window.location.pathname}${params.toString() ? `?${params.toString()}` : ""}`;
window.history.replaceState({}, "", next);
}
function applyUrlState() {
const params = new URLSearchParams(window.location.search);
if (params.get("q")) els.q.value = params.get("q");
if (params.get("category")) els.category.value = params.get("category");
if (params.get("source")) els.source.value = params.get("source");
if (params.get("task")) els.task.value = params.get("task");
if (params.get("status")) els.status.value = params.get("status");
if (params.get("sort")) els.sort.value = params.get("sort");
if (params.get("view")) setView(params.get("view"));
}
function clearFilters() {
els.q.value = "";
els.category.value = "";
els.source.value = "";
els.task.value = "";
els.status.value = "";
els.sort.value = "relevance";
setView("grid");
applyFilters();
}
function applyIntent(intent) {
if (intent === "datasets") {
els.category.value = "dataset";
els.task.value = "";
els.q.value = "";
} else if (intent === "asr") {
els.task.value = "asr";
} else if (intent === "tts") {
els.task.value = "tts";
} else if (intent === "nlp") {
els.task.value = "nlp";
} else if (intent === "mt") {
els.task.value = "";
els.q.value = "translation";
} else if (intent === "verified") {
els.status.value = "verified";
}
applyFilters();
}
async function copySearchLink() {
try {
await navigator.clipboard.writeText(window.location.href);
els.copyLink.textContent = "Link Copied";
setTimeout(() => { els.copyLink.textContent = "Copy Search Link"; }, 1200);
} catch (_error) {
els.copyLink.textContent = "Copy Failed";
setTimeout(() => { els.copyLink.textContent = "Copy Search Link"; }, 1200);
}
}
async function load() {
try {
const res = await fetch("./resources.json", { cache: "no-store" });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const payload = await res.json();
state.all = payload.resources || [];
fillSelect(els.category, uniqSorted(state.all.map((r) => r.category)), "All Categories", formatCategoryLabel);
fillSelect(els.source, uniqSorted(state.all.map((r) => r.source)), "All Sources", formatSourceLabel);
fillSelect(els.status, uniqSorted(state.all.map((r) => r.status)), "All Statuses", formatStatusLabel);
fillSelect(els.task, uniqSorted(state.all.flatMap((r) => r.tasks || [])), "All Tasks", formatTaskLabel);
updateMetrics();
const generated = payload.generated_on ? new Date(payload.generated_on) : null;
els.generatedAt.textContent = generated && !Number.isNaN(generated.getTime())
? `Catalog timestamp: ${generated.toISOString()}`
: "Catalog timestamp: unknown";
applyUrlState();
applyFilters();
} catch (err) {
els.countText.textContent = "Failed to load resources";
els.results.innerHTML = `<li class="empty">Could not load search data. ${esc(String(err))}</li>`;
}
}
[els.q, els.category, els.source, els.task, els.status, els.sort].forEach((el) => {
el.addEventListener("input", applyFilters);
el.addEventListener("change", applyFilters);
});
els.viewGrid.addEventListener("click", () => setView("grid"));
els.viewList.addEventListener("click", () => setView("list"));
els.clearFilters.addEventListener("click", clearFilters);
els.copyLink.addEventListener("click", copySearchLink);
els.intents.forEach((button) => {
button.addEventListener("click", () => applyIntent(button.dataset.intent));
});
load();
</script>
</body>
</html>