NIKKI77's picture
Deploy: GPU-ready HF Space (Docker)
903b444
<!-- Search page (index.html) — input, autocomplete, and theme toggle -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Subtitle Search</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root{
--maxw: 720px;
--radius: 8px;
--blue: #0b5fff;
--blue-hover: #0848c9;
--border: #d0d7de;
}
body {
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
padding: 30px;
color: #222;
background: #fff;
display: flex;
flex-direction: column;
min-height: 100vh;
align-items: center;
padding-top: 22vh;
}
h1 { margin-bottom: 16px; color: #222; }
.search-wrap {
position: relative;
max-width: 600px;
width: 100%;
margin: 0 auto;
}
.search-row {
display: flex;
gap: 0;
align-items: stretch;
}
#queryInput {
flex: 1 1 auto;
min-width: 0;
height: 42px;
box-sizing: border-box;
padding: 10px 12px 10px 40px;
font-size: 16px;
border: 1px solid var(--border);
border-radius: var(--radius) 0 0 var(--radius);
background:
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%2399a3ad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='8'/><line x1='21' y1='21' x2='16.65' y2='16.65'/></svg>")
no-repeat 12px center / 18px 18px #fff;
color: #111;
background-color: #fff;
}
#queryInput::placeholder { color: #6b7280; }
#queryInput:focus {
outline: none;
border-color: var(--blue);
box-shadow: 0 0 0 3px rgba(11,95,255,.15);
}
.search-btn {
height: 42px;
box-sizing: border-box;
line-height: 42px;
padding: 0 16px;
font-size: 15px;
font-weight: 600;
color: #fff;
background: var(--blue);
border: 1px solid var(--blue);
border-radius: 0 var(--radius) var(--radius) 0;
cursor: pointer;
flex: 0 0 auto;
}
.search-btn:hover { background: var(--blue-hover); border-color: var(--blue-hover); }
#suggestions {
border: 1px solid #ccc;
border-radius: 6px;
max-width: var(--maxw);
margin-top: 6px;
padding: 0;
list-style: none;
background: #fff;
position: absolute;
top: calc(42px + 6px);
left: 0;
width: 100%;
z-index: 10;
display: none;
box-shadow: 0 8px 16px rgba(0,0,0,0.08);
overflow: hidden;
color: #111;
}
#suggestions.show { display: block; }
#suggestions li {
padding: 10px 12px;
cursor: pointer;
line-height: 1.3;
}
#suggestions li:hover,
#suggestions li.selected { background: #f0f6ff; }
.no-suggestions {
color: #666;
font-style: italic;
padding: 10px 12px;
}
#loading {
font-size: 14px;
color: #666;
margin-top: 6px;
display: none;
}
body, #queryInput, #suggestions, .search-btn, .theme-toggle {
transition: background-color .2s, color .2s, border-color .2s, box-shadow .2s;
}
.theme-toggle {
position: fixed; top: 16px; right: 16px;
background: #f9f9f9; color: #222;
border: 1px solid #00000022; padding: 8px 12px; border-radius: 8px;
font-weight: 600; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.theme-toggle:hover { box-shadow: 0 6px 18px rgba(0,0,0,0.15); }
html[data-theme="dark"] body { background: #0e0f12; color: #e7e9ee; }
html[data-theme="dark"] h1 { color: #e7e9ee; }
html[data-theme="dark"] #queryInput {
background-color: #15171c;
color: #e7e9ee;
border-color: #333;
}
html[data-theme="dark"] #queryInput::placeholder { color: #b3b8c4; }
html[data-theme="dark"] #suggestions {
background: #15171c;
color: #e7e9ee;
border-color: #333;
box-shadow: 0 8px 16px rgba(0,0,0,0.4);
}
html[data-theme="dark"] #suggestions li { color: #e7e9ee; }
html[data-theme="dark"] #suggestions li:hover,
html[data-theme="dark"] li.selected { background: #1d2026; }
html[data-theme="dark"] .no-suggestions { color: #b3b8c4; }
html[data-theme="dark"] #loading { color: #b3b8c4; }
html[data-theme="dark"] .search-btn {
background: #2d7ed8;
border-color: #2d7ed8;
color: #fff;
}
html[data-theme="dark"] .search-btn:hover {
background: #2464ac;
border-color: #2464ac;
}
html[data-theme="dark"] .theme-toggle {
background: #15171c; color: #e7e9ee; border-color: #333;
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
}
</style>
</head>
<body>
<!-- Theme toggle -->
<button id="themeToggle" class="theme-toggle" onclick="__toggleTheme()">🌙 Dark</button>
<h1>Keyword Search</h1>
<!-- Search form with autocomplete -->
<form action="/search" method="POST" autocomplete="off" role="search" class="search-wrap">
<div class="search-row">
<input
type="text"
name="query"
id="queryInput"
placeholder="Enter your query here (e.g., neural networks)"
size="50"
aria-label="Search input"
aria-autocomplete="list"
aria-controls="suggestions"
aria-expanded="false"
aria-haspopup="listbox">
<button type="submit" class="search-btn">Search</button>
</div>
<div id="loading" aria-live="polite">Loading suggestions…</div>
<ul id="suggestions" role="listbox" aria-labelledby="queryInput"></ul>
</form>
<!-- Version tag -->
<div style="font-size:0.8em; color:#888; margin-top:6px; text-align:center;">
Version 1.1
</div>
<!-- Theme toggle logic -->
<script>
(function() {
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.dataset.theme = saved;
function setTheme(t) {
document.documentElement.dataset.theme = t;
localStorage.setItem('theme', t);
const btn = document.getElementById('themeToggle');
if (btn) btn.textContent = t === 'dark' ? ' Light' : ' Dark';
}
window.__toggleTheme = function() {
const next = (document.documentElement.dataset.theme === 'dark') ? 'light' : 'dark';
setTheme(next);
};
document.addEventListener('DOMContentLoaded', () => {
const cur = document.documentElement.dataset.theme || 'light';
const btn = document.getElementById('themeToggle');
if (btn) btn.textContent = cur === 'dark' ? ' Light' : ' Dark';
});
})();
</script>
<!-- Autocomplete logic -->
<script>
const input = document.getElementById('queryInput');
const suggestionBox = document.getElementById('suggestions');
const loadingEl = document.getElementById('loading');
const escapeHtml = (str) =>
str.replace(/[&<>"']/g, t => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[t]));
let selectedIndex = -1;
input.addEventListener('input', async () => {
const term = input.value.trim();
suggestionBox.classList.remove('show');
suggestionBox.innerHTML = '';
input.setAttribute('aria-expanded', 'false');
selectedIndex = -1;
if (term.length < 2) {
loadingEl.style.display = 'none';
return;
}
loadingEl.style.display = 'block';
try {
const res = await fetch(`/autocomplete?term=${encodeURIComponent(term)}`);
const suggestions = await res.json();
loadingEl.style.display = 'none';
if (!Array.isArray(suggestions) || suggestions.length === 0) {
suggestionBox.innerHTML = '<li class="no-suggestions" role="option" aria-disabled="true">No suggestions found</li>';
suggestionBox.classList.add('show');
input.setAttribute('aria-expanded', 'true');
return;
}
suggestionBox.innerHTML = suggestions
.map(s => `<li role="option">${escapeHtml(s)}</li>`)
.join('');
suggestionBox.classList.add('show');
input.setAttribute('aria-expanded', 'true');
} catch (err) {
console.error('Autocomplete error:', err);
loadingEl.style.display = 'none';
}
});
suggestionBox.addEventListener('click', (e) => {
const li = e.target.closest('li[role="option"]');
if (!li || li.classList.contains('no-suggestions')) return;
input.value = li.textContent;
suggestionBox.classList.remove('show');
suggestionBox.innerHTML = '';
input.setAttribute('aria-expanded', 'false');
});
input.addEventListener('keydown', (e) => {
const items = suggestionBox.querySelectorAll('li[role="option"]:not(.no-suggestions)');
if (!items.length) return;
if (e.key === 'ArrowDown') {
e.preventDefault();
selectedIndex = Math.min(selectedIndex + 1, items.length - 1);
updateSelection(items);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
selectedIndex = Math.max(selectedIndex - 1, 0);
updateSelection(items);
} else if (e.key === 'Enter') {
if (selectedIndex >= 0) {
e.preventDefault();
input.value = items[selectedIndex].textContent;
suggestionBox.classList.remove('show');
suggestionBox.innerHTML = '';
input.setAttribute('aria-expanded', 'false');
}
} else if (e.key === 'Escape') {
suggestionBox.classList.remove('show');
suggestionBox.innerHTML = '';
input.setAttribute('aria-expanded', 'false');
}
});
function updateSelection(items) {
items.forEach((item, i) => item.classList.toggle('selected', i === selectedIndex));
const active = items[selectedIndex];
if (active) active.scrollIntoView({ block: 'nearest' });
}
document.addEventListener('click', (e) => {
if (!e.target.closest('form')) {
suggestionBox.classList.remove('show');
suggestionBox.innerHTML = '';
input.setAttribute('aria-expanded', 'false');
}
});
</script>
</body>
</html>