// ═══════════════════════════════════════════════════════════
// SOURCES
// ═══════════════════════════════════════════════════════════
const SOURCES = {
dictionary: {
bang: "!d",
count: 0,
enabled: true,
fetch: async (q) => {
const r = await fetch(
`https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(q)}`,
);
if (!r.ok) return [];
const d = await r.json();
return d.slice(0, 3).flatMap((e) =>
e.meanings.slice(0, 2).map((m) => ({
meta: { phonetic: e.phonetic },
snippet: m.definitions[0]?.definition || "",
source: "dictionary",
title: `${e.word} [${m.partOfSpeech}]`,
url: e.sourceUrls?.[0] || "#",
})),
);
},
name: "DICTIONARY",
},
github: {
bang: "!g",
count: 0,
enabled: true,
fetch: async (q) => {
const r = await fetch(
`https://api.github.com/search/repositories?q=${encodeURIComponent(q)}&per_page=8&sort=stars`,
);
const d = await r.json();
return (d.items || []).map((i) => ({
meta: { forks: i.forks_count, lang: i.language, stars: i.stargazers_count },
snippet: i.description || "NO DESCRIPTION",
source: "github",
title: i.full_name,
url: i.html_url,
}));
},
name: "GITHUB",
},
hackernews: {
bang: "!hn",
count: 0,
enabled: true,
fetch: async (q) => {
const r = await fetch(
`https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(q)}&hitsPerPage=8`,
);
const d = await r.json();
return (d.hits || [])
.filter((h) => h.title)
.map((h) => ({
meta: { comments: h.num_comments, points: h.points },
snippet: `${h.points || 0} POINTS // ${h.author}`,
source: "hackernews",
title: h.title,
url: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`,
}));
},
name: "HACKERNEWS",
},
news: {
bang: "!n",
count: 0,
enabled: true,
fetch: async (q) => {
// Combine HN, Reddit News, and Wiki Current Events logic
const p1 = fetch(
`https://hn.algolia.com/api/v1/search_by_date?query=${encodeURIComponent(q)}&tags=story&hitsPerPage=6`,
)
.then((r) => r.json())
.catch(() => ({ hits: [] }));
const p2 = fetch(
`https://www.reddit.com/r/worldnews+news+technology/search.json?q=${encodeURIComponent(q)}&restrict_sr=1&sort=new&limit=6`,
)
.then((r) => r.json())
.catch(() => ({ data: { children: [] } }));
const [hn, rd] = await Promise.all([p1, p2]);
const hnRes = (hn.hits || []).map((h) => ({
meta: { date: h.created_at_i, lang: "HN" },
snippet: `HACKERNEWS // ${h.points || 0} PTS // ${timeAgo(h.created_at_i * 1000)}`,
source: "news",
title: h.title,
url: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`,
}));
const rdRes = (rd.data?.children || []).map((c) => ({
meta: { date: c.data.created_utc, lang: "RD" },
snippet: `REDDIT r/${c.data.subreddit.toUpperCase()} // ${c.data.score} UPVOTES`,
source: "news",
title: c.data.title,
url: `https://reddit.com${c.data.permalink}`,
}));
// Interleave/Sort by recency
return [...hnRes, ...rdRes].sort((a, b) => b.meta.date - a.meta.date);
},
name: "AGGREGATE NEWS",
},
npm: {
bang: "!npm",
count: 0,
enabled: true,
fetch: async (q) => {
const r = await fetch(
`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(q)}&size=8`,
);
const d = await r.json();
return (d.objects || []).map((o) => ({
meta: { version: o.package.version },
snippet: o.package.description || "NO DESCRIPTION",
source: "npm",
title: o.package.name,
url: `https://npmjs.com/package/${o.package.name}`,
}));
},
name: "NPM",
},
openlibrary: {
bang: "!b",
count: 0,
enabled: true,
fetch: async (q) => {
const r = await fetch(
`https://openlibrary.org/search.json?q=${encodeURIComponent(q)}&limit=6`,
);
const d = await r.json();
return (d.docs || []).map((b) => ({
meta: { year: b.first_publish_year },
snippet: `BY ${(b.author_name || ["UNKNOWN"]).join(", ").toUpperCase()}`,
source: "openlibrary",
title: b.title,
url: `https://openlibrary.org${b.key}`,
}));
},
name: "OPENLIBRARY",
},
reddit: {
bang: "!r",
count: 0,
enabled: true,
fetch: async (q) => {
const r = await fetch(
`https://www.reddit.com/search.json?q=${encodeURIComponent(q)}&limit=8`,
);
const d = await r.json();
return (d.data?.children || []).map((c) => ({
meta: { comments: c.data.num_comments, score: c.data.score },
snippet: c.data.selftext?.substring(0, 200) || `r/${c.data.subreddit}`,
source: "reddit",
title: c.data.title,
url: `https://reddit.com${c.data.permalink}`,
}));
},
name: "REDDIT",
},
stackoverflow: {
bang: "!so",
count: 0,
enabled: true,
fetch: async (q) => {
const r = await fetch(
`https://api.stackexchange.com/2.3/search?order=desc&sort=relevance&intitle=${encodeURIComponent(q)}&site=stackoverflow&pagesize=8`,
);
const d = await r.json();
return (d.items || []).map((i) => ({
meta: { answers: i.answer_count, score: i.score },
snippet: `${i.answer_count} ANSWERS // ${i.view_count} VIEWS`,
source: "stackoverflow",
title: decodeHTML(i.title),
url: i.link,
}));
},
name: "STACKOVERFLOW",
},
wikipedia: {
bang: "!w",
count: 0,
enabled: true,
fetch: async (q) => {
const r = await fetch(
`https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(q)}&format=json&origin=*&srlimit=8`,
);
const d = await r.json();
return (d.query?.search || []).map((i) => ({
meta: { words: i.wordcount },
snippet: i.snippet.replace(/<[^>]*>/g, ""),
source: "wikipedia",
title: i.title,
url: `https://en.wikipedia.org/wiki/${encodeURIComponent(i.title)}`,
}));
},
instant: async (q) => {
const s = await fetch(
`https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(q)}&format=json&origin=*&srlimit=1`,
);
const sd = await s.json();
if (!sd.query?.search?.length) return null;
const title = sd.query.search[0].title;
const r = await fetch(
`https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&titles=${encodeURIComponent(title)}&format=json&origin=*`,
);
const d = await r.json();
const page = Object.values(d.query.pages)[0];
return {
content: page.extract,
source: "WIKIPEDIA",
title: page.title,
url: `https://en.wikipedia.org/wiki/${encodeURIComponent(title)}`,
};
},
name: "WIKIPEDIA",
},
};
const STOCK_EXCHANGES = {
AAPL: "NASDAQ",
ADA: "CRYPTO",
AMD: "NASDAQ",
AMZN: "NASDAQ",
BAC: "NYSE",
BTC: "CRYPTO",
DIS: "NYSE",
DOGE: "CRYPTO",
ETH: "CRYPTO",
EURUSD: "FX",
GBPUSD: "FX",
GOOG: "NASDAQ",
GOOGL: "NASDAQ",
INTC: "NASDAQ",
JNJ: "NYSE",
JPM: "NYSE",
KO: "NYSE",
META: "NASDAQ",
MSFT: "NASDAQ",
NFLX: "NASDAQ",
NVDA: "NASDAQ",
PYPL: "NASDAQ",
QQQ: "NASDAQ",
SOL: "CRYPTO",
SPY: "AMEX",
TSLA: "NASDAQ",
USDJPY: "FX",
V: "NYSE",
WMT: "NYSE",
XOM: "NYSE",
XRP: "CRYPTO",
};
function getSymbol(ticker) {
const t = ticker.toUpperCase();
const ex = STOCK_EXCHANGES[t];
if (ex === "CRYPTO") return `BINANCE:${t}USDT`;
if (ex === "FX") return `FX:${t}`;
if (ex) return `${ex}:${t}`;
return `NASDAQ:${t}`;
}
// ═══════════════════════════════════════════════════════════
// STATE
// ═══════════════════════════════════════════════════════════
const state = {
calcExpr: "",
calcResult: "0",
hasSearched: false,
history: JSON.parse(localStorage.getItem("sh") || "[]"),
query: "",
results: [],
watchlist: JSON.parse(localStorage.getItem("sw") || '["TSLA", "AAPL", "BTC", "ETH"]'),
};
const $ = (id) => document.getElementById(id);
const searchInput = $("search-input");
const resultsEl = $("results");
const heroEl = $("hero");
const mainEl = $("main-content");
const statsBar = $("stats-bar");
// ═══════════════════════════════════════════════════════════
// INIT
// ═══════════════════════════════════════════════════════════
function init() {
renderSources();
renderHistory();
renderWatchlist();
initTheme();
updateClocks();
setInterval(updateClocks, 1000);
setupCalc();
setupListeners();
setupWatchlistListeners();
}
function renderSources() {
$("sources-list").innerHTML = Object.entries(SOURCES)
.map(
([k, s]) => `
${s.name} ${s.count}
`,
)
.join("");
}
function toggleSource(k) {
SOURCES[k].enabled = !SOURCES[k].enabled;
renderSources();
if (state.query) search(state.query);
}
function toggleWidget(id) {
$(id).classList.toggle("open");
}
// ═══════════════════════════════════════════════════════════
// THEME
// ═══════════════════════════════════════════════════════════
function initTheme() {
const t = localStorage.getItem("theme");
if (t === "light") document.body.classList.add("light-mode");
}
function toggleTheme() {
document.body.classList.toggle("light-mode");
const isLight = document.body.classList.contains("light-mode");
localStorage.setItem("theme", isLight ? "light" : "dark");
}
// ═══════════════════════════════════════════════════════════
// WATCHLIST
// ═══════════════════════════════════════════════════════════
function renderWatchlist() {
const c = $("watchlist-container");
if (!state.watchlist.length) {
c.innerHTML = 'EMPTY
';
return;
}
c.innerHTML = state.watchlist
.map((t) => {
const ex = STOCK_EXCHANGES[t] || "STOCK";
return `
${t} ${ex}
×
`;
})
.join("");
}
function addTicker() {
const val = $("watchlist-input").value.toUpperCase().trim();
if (val && !state.watchlist.includes(val)) {
state.watchlist.push(val);
localStorage.setItem("sw", JSON.stringify(state.watchlist));
renderWatchlist();
$("watchlist-input").value = "";
}
}
window.removeTicker = (e, t) => {
e.stopPropagation(); // Prevent search trigger
state.watchlist = state.watchlist.filter((i) => i !== t);
localStorage.setItem("sw", JSON.stringify(state.watchlist));
renderWatchlist();
};
function setupWatchlistListeners() {
$("watchlist-add-btn").onclick = addTicker;
$("watchlist-input").addEventListener("keydown", (e) => {
if (e.key === "Enter") addTicker();
});
}
// ═══════════════════════════════════════════════════════════
// SEARCH
// ═══════════════════════════════════════════════════════════
async function search(query) {
if (!query.trim()) return goHome();
// Show results view
if (!state.hasSearched) {
state.hasSearched = true;
heroEl.classList.add("compact");
mainEl.style.display = "grid";
statsBar.classList.add("visible");
}
// Stock ticker
const stockMatch = query.match(/^\$([A-Za-z]{1,5})$/);
if (stockMatch) {
showStockWidget(stockMatch[1].toUpperCase());
addHistory(query);
return;
}
// Bangs
const bangMatch = query.match(/^!(\w+)\s*(.*)/);
if (bangMatch) {
const bang = `!${bangMatch[1]}`;
const q = bangMatch[2];
const src = Object.entries(SOURCES).find(([_, s]) => s.bang === bang);
if (src && q) {
Object.keys(SOURCES).forEach((k) => (SOURCES[k].enabled = k === src[0]));
renderSources();
query = q;
}
}
// Calc
if (query.startsWith("=")) {
showCalcResult(query.slice(1));
return;
}
state.query = query;
showLoading();
addHistory(query);
const start = performance.now();
const enabled = Object.entries(SOURCES).filter(([_, s]) => s.enabled);
fetchInstant(query);
const promises = enabled.map(async ([k, s]) => {
try {
const r = await s.fetch(query);
SOURCES[k].count = r.length;
$(`c-${k}`).textContent = r.length;
return r;
} catch (e) {
return [];
}
});
const all = await Promise.all(promises);
state.results = all.flat();
const elapsed = Math.round(performance.now() - start);
$("stat-results").textContent = state.results.length;
$("stat-sources").textContent = enabled.length;
$("stat-time").textContent = `${elapsed}MS`;
$("results-label").textContent = `${state.results.length} RESULTS`;
$("results-info").textContent = `${elapsed}MS // ${enabled.length} SOURCES`;
renderResults();
}
function goHome() {
state.hasSearched = false;
heroEl.classList.remove("compact");
mainEl.style.display = "none";
statsBar.classList.remove("visible");
searchInput.value = "";
}
// ═══════════════════════════════════════════════════════════
// STOCK WIDGET
// ═══════════════════════════════════════════════════════════
function showStockWidget(ticker) {
const symbol = getSymbol(ticker);
const widgetId = `tv_${Math.random().toString(36).substr(2, 9)}`;
const isLight = document.body.classList.contains("light-mode");
resultsEl.innerHTML = `
`;
$("results-label").textContent = `STOCK: ${ticker}`;
$("results-info").textContent = symbol;
loadTradingView(widgetId, symbol, isLight);
}
function loadTradingView(containerId, symbol, isLight) {
if (!window.TradingView) {
const s = document.createElement("script");
s.src = "https://s3.tradingview.com/tv.js";
s.onload = () => createChart(containerId, symbol, isLight);
document.head.appendChild(s);
} else {
createChart(containerId, symbol, isLight);
}
}
function createChart(containerId, symbol, isLight) {
new TradingView.widget({
allow_symbol_change: true,
backgroundColor: isLight ? "rgba(244, 244, 245, 1)" : "rgba(0,0,0,1)",
container_id: containerId,
enable_publishing: false,
gridColor: isLight ? "rgba(220, 220, 220, 1)" : "rgba(30,30,30,1)",
height: 500,
interval: "D",
locale: "en",
studies: ["Volume@tv-basicstudies"],
style: "1",
symbol: symbol,
theme: isLight ? "light" : "dark",
timezone: "Etc/UTC",
toolbar_bg: isLight ? "#f4f4f5" : "#000",
width: "100%",
});
}
async function fetchInstant(query) {
if (/^[a-zA-Z]+$/.test(query) && SOURCES.dictionary.enabled) {
try {
const r = await fetch(
`https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(query)}`,
);
if (r.ok) {
const d = await r.json();
if (d[0]) {
const meanings = d[0].meanings
.slice(0, 3)
.map((m) => `[${m.partOfSpeech.toUpperCase()}] ${m.definitions[0]?.definition}`)
.join("\n\n");
showInstant({
content: meanings,
source: "DICTIONARY",
title: d[0].word.toUpperCase() + (d[0].phonetic ? ` ${d[0].phonetic}` : ""),
});
return;
}
}
} catch {}
}
if (SOURCES.wikipedia.enabled) {
const w = await SOURCES.wikipedia.instant?.(query);
if (w) showInstant(w);
}
}
function showInstant(data) {
const el = document.createElement("div");
el.className = "instant-box";
el.innerHTML = `
INSTANT ANSWER
${esc(data.title)}
${esc(data.content)
.split("\n")
.map((p) => `
${p}
`)
.join("")}
`;
resultsEl.innerHTML = "";
resultsEl.appendChild(el);
}
function renderResults() {
if (state.results.length === 0) {
resultsEl.innerHTML = `NO RESULTS
TRY DIFFERENT KEYWORDS OR ENABLE MORE SOURCES
`;
return;
}
const instant = resultsEl.querySelector(".instant-box");
resultsEl.innerHTML = "";
if (instant) resultsEl.appendChild(instant);
state.results.forEach((r, i) => {
const el = document.createElement("div");
el.className = "result";
el.innerHTML = `
${String(i + 1).padStart(2, "0")}
${r.source.toUpperCase()}
${esc(r.title)}
${esc(r.url)}
${highlight(esc(r.snippet), state.query)}
${
r.meta
? `
${r.meta.stars !== undefined ? `★ ${fmt(r.meta.stars)}` : ""}
${r.meta.forks !== undefined ? `⑂ ${fmt(r.meta.forks)}` : ""}
${r.meta.score !== undefined ? `↑ ${fmt(r.meta.score)}` : ""}
${r.meta.comments !== undefined ? `◨ ${fmt(r.meta.comments)}` : ""}
${r.meta.words !== undefined ? `◎ ${fmt(r.meta.words)}W` : ""}
${r.meta.lang ? `${r.meta.lang.toUpperCase()}` : ""}
${r.meta.version ? `V${r.meta.version}` : ""}
`
: ""
}
`;
resultsEl.appendChild(el);
});
}
function showLoading() {
resultsEl.innerHTML = Array(4)
.fill(
``,
)
.join("");
}
function showCalcResult(expr) {
try {
const result = Function(`"use strict";return (${expr.replace(/\^/g, "**")})`)();
resultsEl.innerHTML = `CALCULATOR
${esc(expr)} = ${result}
`;
} catch {
toast("INVALID EXPRESSION");
}
}
// ═══════════════════════════════════════════════════════════
// CALCULATOR
// ═══════════════════════════════════════════════════════════
function setupCalc() {
document.querySelectorAll(".calc-btn").forEach((btn) => {
btn.onclick = () => {
const v = btn.dataset.v;
if (v === "C") {
state.calcExpr = "";
state.calcResult = "0";
} else if (v === "=") {
try {
state.calcResult = Function(
`"use strict";return (${state.calcExpr.replace(/\^/g, "**")})`,
)();
} catch {
state.calcResult = "ERR";
}
} else {
state.calcExpr += v;
}
$("calc-expr").textContent = state.calcExpr;
$("calc-result").textContent = state.calcResult;
};
});
}
// ═══════════════════════════════════════════════════════════
// TOOLS
// ═══════════════════════════════════════════════════════════
function toolUUID() {
const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
});
copy(uuid);
toast(`UUID: ${uuid.substring(0, 13)}...`);
}
let timerInterval,
timerSec = 0;
function toolTimer() {
if (!state.hasSearched) {
state.hasSearched = true;
heroEl.classList.add("compact");
mainEl.style.display = "grid";
statsBar.classList.add("visible");
}
resultsEl.innerHTML = ``;
$("results-label").textContent = "TIMER";
}
window.startTimer = () => {
if (timerInterval) return;
timerInterval = setInterval(() => {
timerSec++;
const h = String(Math.floor(timerSec / 3600)).padStart(2, "0");
const m = String(Math.floor((timerSec % 3600) / 60)).padStart(2, "0");
const s = String(timerSec % 60).padStart(2, "0");
$("timer-display").textContent = `${h}:${m}:${s}`;
}, 1000);
};
window.stopTimer = () => {
clearInterval(timerInterval);
timerInterval = null;
};
window.resetTimer = () => {
stopTimer();
timerSec = 0;
$("timer-display").textContent = "00:00:00";
};
function toolBase64() {
if (!state.hasSearched) {
state.hasSearched = true;
heroEl.classList.add("compact");
mainEl.style.display = "grid";
statsBar.classList.add("visible");
}
resultsEl.innerHTML = ``;
$("results-label").textContent = "BASE64";
}
function toolJSON() {
if (!state.hasSearched) {
state.hasSearched = true;
heroEl.classList.add("compact");
mainEl.style.display = "grid";
statsBar.classList.add("visible");
}
resultsEl.innerHTML = ``;
$("results-label").textContent = "JSON";
}
window.formatJSON = () => {
try {
$("json-output").textContent = JSON.stringify(JSON.parse($("json-input").value), null, 2);
} catch (e) {
$("json-output").textContent = `INVALID: ${e.message}`;
}
};
window.minifyJSON = () => {
try {
$("json-output").textContent = JSON.stringify(JSON.parse($("json-input").value));
} catch (e) {
$("json-output").textContent = `INVALID: ${e.message}`;
}
};
function toolHash() {
if (!state.hasSearched) {
state.hasSearched = true;
heroEl.classList.add("compact");
mainEl.style.display = "grid";
statsBar.classList.add("visible");
}
resultsEl.innerHTML = ``;
$("results-label").textContent = "HASH";
}
window.genHash = async () => {
const data = new TextEncoder().encode($("hash-input").value);
const hash = await crypto.subtle.digest("SHA-256", data);
$("hash-output").textContent = Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
};
function toolColor() {
if (!state.hasSearched) {
state.hasSearched = true;
heroEl.classList.add("compact");
mainEl.style.display = "grid";
statsBar.classList.add("visible");
}
resultsEl.innerHTML = ``;
const updateColor = () => {
const hex = $("color-input").value;
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
$("color-output").innerHTML =
`HEX: ${hex}
RGB: ${r}, ${g}, ${b}
HSL: ${rgbToHsl(r, g, b)}`;
};
$("color-input").oninput = updateColor;
updateColor();
$("results-label").textContent = "COLOR";
}
function rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b),
min = Math.min(r, g, b);
let h,
s,
l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
break;
case g:
h = ((b - r) / d + 2) / 6;
break;
case b:
h = ((r - g) / d + 4) / 6;
break;
}
}
return `${Math.round(h * 360)}°, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%`;
}
function toolPassword() {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
let pass = "";
for (let i = 0; i < 20; i++) pass += chars[Math.floor(Math.random() * chars.length)];
copy(pass);
toast(`PASSWORD: ${pass.substring(0, 10)}...`);
}
function toolLoremIpsum() {
const lorem =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.";
copy(lorem);
toast("LOREM IPSUM COPIED");
}
// ═══════════════════════════════════════════════════════════
// HISTORY
// ═══════════════════════════════════════════════════════════
function addHistory(q) {
state.history = state.history.filter((h) => h.q !== q);
state.history.unshift({ q, t: Date.now() });
state.history = state.history.slice(0, 20);
localStorage.setItem("sh", JSON.stringify(state.history));
renderHistory();
}
function renderHistory() {
const list = $("history-list");
if (!state.history.length) {
list.innerHTML = 'NO HISTORY
';
return;
}
list.innerHTML = state.history
.slice(0, 6)
.map(
(h) =>
`${esc(h.q)}${timeAgo(h.t)}
`,
)
.join("");
}
// ═══════════════════════════════════════════════════════════
// CLOCKS
// ═══════════════════════════════════════════════════════════
function updateClocks() {
const now = new Date();
$("clock").textContent = now.toLocaleTimeString("en-GB");
const zones = [
{ label: "LOCAL", tz: Intl.DateTimeFormat().resolvedOptions().timeZone },
{ label: "UTC", tz: "UTC" },
{ label: "NYC", tz: "America/New_York" },
{ label: "TOKYO", tz: "Asia/Tokyo" },
];
$("world-clocks").innerHTML = zones
.map(
(z) =>
`${new Date().toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", timeZone: z.tz })}
${z.label}
`,
)
.join("");
}
// ═══════════════════════════════════════════════════════════
// LISTENERS
// ═══════════════════════════════════════════════════════════
function setupListeners() {
let debounce;
searchInput.addEventListener("input", (e) => {
clearTimeout(debounce);
debounce = setTimeout(() => search(e.target.value), 300);
});
searchInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
clearTimeout(debounce);
search(searchInput.value);
}
if (e.key === "Escape") goHome();
});
document.addEventListener("keydown", (e) => {
if (
e.key === "/" &&
document.activeElement !== searchInput &&
document.activeElement !== $("watchlist-input")
) {
e.preventDefault();
searchInput.focus();
}
if (e.key === "Escape") goHome();
});
$("clear-btn").onclick = goHome;
$("voice-btn").onclick = voiceSearch;
}
function voiceSearch() {
if (!("webkitSpeechRecognition" in window || "SpeechRecognition" in window)) {
toast("VOICE NOT SUPPORTED");
return;
}
const Sr = window.SpeechRecognition || window.webkitSpeechRecognition;
const r = new Sr();
r.lang = "en-US";
$("voice-btn").style.background = "#fff";
$("voice-btn").style.color = "#000";
r.onresult = (e) => {
searchInput.value = e.results[0][0].transcript;
search(searchInput.value);
};
r.onend = () => {
$("voice-btn").style.background = "";
$("voice-btn").style.color = "";
};
r.start();
}
// ═══════════════════════════════════════════════════════════
// UTILITIES
// ═══════════════════════════════════════════════════════════
function esc(s) {
if (!s) return "";
return s.replace(
/[&<>"']/g,
(m) => ({ "'": "'", '"': """, "&": "&", "<": "<", ">": ">" })[m],
);
}
function decodeHTML(s) {
const t = document.createElement("textarea");
t.innerHTML = s;
return t.value;
}
function highlight(text, query) {
if (!query) return text;
const words = query.split(/\s+/).filter((w) => w.length > 2);
let result = text;
words.forEach((w) => {
result = result.replace(new RegExp(`(${w})`, "gi"), "$1");
});
return result;
}
function fmt(n) {
if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;
return n?.toString() || "0";
}
function timeAgo(ts) {
const s = Math.floor((Date.now() - ts) / 1000);
if (s < 60) return "NOW";
if (s < 3600) return `${Math.floor(s / 60)}M`;
if (s < 86400) return `${Math.floor(s / 3600)}H`;
return `${Math.floor(s / 86400)}D`;
}
function copy(text) {
navigator.clipboard.writeText(text);
toast("COPIED");
}
function toast(msg) {
const t = document.createElement("div");
t.className = "toast";
t.textContent = msg;
$("toast-container").appendChild(t);
setTimeout(() => t.remove(), 3000);
}
function searchFor(q) {
searchInput.value = q;
search(q);
}
function insertBang(bang) {
searchInput.value = `${bang} `;
searchInput.focus();
}
init();