From 58e705473cfe5078f109267a1164db81c7fb2da7 Mon Sep 17 00:00:00 2001 From: Petri Hienonen Date: Sun, 14 Dec 2025 11:35:04 +0200 Subject: Update --- flake.nix | 7 +- index.html | 264 --------------- main.js | 972 ----------------------------------------------------- serve.go | 11 + src/index.html | 264 +++++++++++++++ src/main.js | 972 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/style.css | 1022 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 1022 -------------------------------------------------------- 8 files changed, 2275 insertions(+), 2259 deletions(-) delete mode 100644 index.html delete mode 100644 main.js create mode 100644 serve.go create mode 100644 src/index.html create mode 100644 src/main.js create mode 100644 src/style.css delete mode 100644 style.css diff --git a/flake.nix b/flake.nix index 2cf8f59..2838c53 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,12 @@ pkgs = nixpkgsFor.${system}; in { - default = pkgs.mkShell { buildInputs = with pkgs; [ biome ]; }; + default = pkgs.mkShell { + buildInputs = with pkgs; [ + biome + go + ]; + }; } ); }; diff --git a/index.html b/index.html deleted file mode 100644 index 5573e60..0000000 --- a/index.html +++ /dev/null @@ -1,264 +0,0 @@ - - - - - - SEARCH - - - - - - -
-
SEARCH
-
Universal Search Engine
- -
- -
- -
- !N NEWS - $TSLA - !W WIKI - !G GITHUB - !R REDDIT - UUID - TIMER -
-
- - -
-
- RESULTS - 0 -
-
- SOURCES - 0 -
-
- LATENCY - 0MS -
-
- - -
- - - - -
-
- READY - -
-
-
- - - -
- - - - -
- - - - diff --git a/main.js b/main.js deleted file mode 100644 index 8bba38e..0000000 --- a/main.js +++ /dev/null @@ -1,972 +0,0 @@ -// ═══════════════════════════════════════════════════════════ -// 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 = ` -
-
-
-

${ticker}

-
${symbol}
-
-
- - -
-
-
-
- `; - - $("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("")}
-
- SOURCE: ${esc(data.source)} - ${data.url ? `VIEW FULL →` : ""} -
- `; - 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( - `
FETCHING
`, - ) - .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 = `
TIMER
00:00:00
`; - $("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 = `
BASE64
OUTPUT
`; - $("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 = `
JSON
OUTPUT
`; - $("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 = `
SHA-256 HASH
OUTPUT
`; - $("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 = `
COLOR
`; - 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(); diff --git a/serve.go b/serve.go new file mode 100644 index 0000000..f35bb0e --- /dev/null +++ b/serve.go @@ -0,0 +1,11 @@ +package main + +import ( + "net/http" +) + +func main() { + fs := http.FileServer(http.Dir("./src")) + http.Handle("/", fs) + http.ListenAndServe(":8082", nil) +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..5573e60 --- /dev/null +++ b/src/index.html @@ -0,0 +1,264 @@ + + + + + + SEARCH + + + + + + +
+
SEARCH
+
Universal Search Engine
+ +
+ +
+ +
+ !N NEWS + $TSLA + !W WIKI + !G GITHUB + !R REDDIT + UUID + TIMER +
+
+ + +
+
+ RESULTS + 0 +
+
+ SOURCES + 0 +
+
+ LATENCY + 0MS +
+
+ + +
+ + + + +
+
+ READY + +
+
+
+ + + +
+ + + + +
+ + + + diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..e9bc9f7 --- /dev/null +++ b/src/main.js @@ -0,0 +1,972 @@ +// ═══════════════════════════════════════════════════════════ +// 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 = ` +
+
+
+

${ticker}

+
${symbol}
+
+
+ + +
+
+
+
+ `; + + $("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("")}
+
+ SOURCE: ${esc(data.source)} + ${data.url ? `VIEW FULL →` : ""} +
+ `; + 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( + `
FETCHING
`, + ) + .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 = `
TIMER
00:00:00
`; + $("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 = `
BASE64
OUTPUT
`; + $("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 = `
JSON
OUTPUT
`; + $("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 = `
SHA-256 HASH
OUTPUT
`; + $("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 = `
COLOR
`; + 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(); diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..c051103 --- /dev/null +++ b/src/style.css @@ -0,0 +1,1022 @@ +@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap"); + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + /* DARK MODE (DEFAULT) */ + --bg: #000000; + --fg: #ffffff; + --gray: #666666; + --panel: #0a0a0a; + --border-color: #ffffff; + --border-thin-color: #333333; + --input-ph: #333333; + --hover-bg: #111111; +} + +body.light-mode { + /* LIGHT MODE (INVERTED) */ + --bg: #f4f4f5; + --fg: #09090b; + --gray: #52525b; + --panel: #e4e4e7; + --border-color: #09090b; + --border-thin-color: #a1a1aa; + --input-ph: #a1a1aa; + --hover-bg: #d4d4d8; +} + +/* Variables used in CSS */ +:root { + --border: 3px solid var(--border-color); + --border-thin: 1px solid var(--border-thin-color); +} + +::selection { + background: var(--fg); + color: var(--bg); +} + +html { + font-size: 14px; +} + +body { + font-family: "IBM Plex Mono", monospace; + background: var(--bg); + color: var(--fg); + min-height: 100vh; + cursor: crosshair; + transition: + background 0.2s, + color 0.2s; +} + +a { + color: inherit; + text-decoration: none; +} + +/* HERO SEARCH */ +.hero { + min-height: 50vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 40px 20px; + border-bottom: var(--border); + position: relative; +} + +.hero.compact { + min-height: auto; + padding: 20px; + position: sticky; + top: 0; + background: var(--bg); + z-index: 1000; +} + +.logo-large { + font-size: 72px; + font-weight: 700; + letter-spacing: -4px; + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 10px; +} + +.hero.compact .logo-large { + font-size: 24px; + letter-spacing: -1px; + margin-bottom: 0; + position: absolute; + left: 30px; + top: 50%; + transform: translateY(-50%); +} + +.logo-large::before { + content: ">"; + animation: blink 1s infinite; +} + +@keyframes blink { + 0%, + 49% { + opacity: 1; + } + 50%, + 100% { + opacity: 0; + } +} + +.tagline { + font-size: 12px; + color: var(--gray); + letter-spacing: 4px; + text-transform: uppercase; + margin-bottom: 50px; +} + +.hero.compact .tagline { + display: none; +} + +.search-container { + width: 100%; + max-width: 800px; +} + +.search-box { + display: flex; + border: var(--border); + background: var(--bg); +} + +.search-prefix { + padding: 25px 25px; + font-size: 28px; + font-weight: 700; + border-right: var(--border); + background: var(--fg); + color: var(--bg); +} + +#search-input { + flex: 1; + padding: 25px 30px; + font-size: 24px; + font-family: inherit; + font-weight: 600; + background: transparent; + color: var(--fg); + border: none; + outline: none; + letter-spacing: -0.5px; +} + +#search-input::placeholder { + color: var(--input-ph); +} + +.search-actions { + display: flex; +} + +.action-btn { + width: 80px; + background: transparent; + border: none; + border-left: var(--border); + color: var(--fg); + font-size: 20px; + cursor: pointer; + transition: all 0.1s; + font-family: inherit; +} + +.action-btn:hover { + background: var(--fg); + color: var(--bg); +} + +.hero.compact .search-container { + max-width: 600px; +} + +.hero.compact .search-box { + border-width: 2px; +} + +.hero.compact .search-prefix { + padding: 15px 20px; + font-size: 20px; + border-width: 2px; +} + +.hero.compact #search-input { + padding: 15px 20px; + font-size: 18px; +} + +.hero.compact .action-btn { + width: 60px; + border-width: 2px; +} + +/* SHORTCUTS */ +.shortcuts { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 10px; + margin-top: 30px; +} + +.hero.compact .shortcuts { + display: none; +} + +.shortcut { + padding: 12px 20px; + border: 2px solid var(--border-thin-color); + font-size: 11px; + letter-spacing: 2px; + cursor: pointer; + transition: all 0.1s; + text-transform: uppercase; +} + +.shortcut:hover { + border-color: var(--fg); + background: var(--fg); + color: var(--bg); +} + +/* STATS BAR */ +.stats-bar { + display: none; + justify-content: center; + gap: 40px; + padding: 15px; + font-size: 11px; + letter-spacing: 2px; + color: var(--gray); + border-bottom: var(--border-thin); +} + +.stats-bar.visible { + display: flex; +} + +.stat-item { + display: flex; + gap: 10px; +} + +.stat-value { + color: var(--fg); + font-weight: 700; +} + +/* MAIN LAYOUT */ +.main { + display: grid; + grid-template-columns: 300px 1fr 300px; + min-height: 50vh; +} + +@media (max-width: 1200px) { + .main { + grid-template-columns: 1fr; + } + .sidebar-left, + .sidebar-right { + border: none; + border-bottom: var(--border); + } +} + +/* SIDEBARS */ +.sidebar-left, +.sidebar-right { + border-right: var(--border); +} + +.sidebar-right { + border-right: none; + border-left: var(--border); +} + +/* WIDGET */ +.widget { + border-bottom: var(--border-thin); +} + +.widget-header { + padding: 15px 20px; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + transition: background 0.1s; +} + +.widget-header:hover { + background: var(--hover-bg); +} + +.widget-title { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 3px; + color: var(--gray); +} + +.widget-toggle { + font-size: 16px; + color: var(--gray); + transition: transform 0.2s; +} + +.widget.open .widget-toggle { + transform: rotate(45deg); +} + +.widget-body { + display: none; + padding: 20px; + border-top: var(--border-thin); +} + +.widget.open .widget-body { + display: block; +} + +/* TOOLS GRID */ +.tools-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; +} + +.tool-btn { + padding: 15px 10px; + border: 2px solid var(--border-thin-color); + background: transparent; + color: var(--gray); + font-family: inherit; + font-size: 9px; + text-transform: uppercase; + letter-spacing: 1px; + cursor: pointer; + transition: all 0.1s; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.tool-btn:hover { + background: var(--fg); + color: var(--bg); + border-color: var(--fg); +} + +.tool-btn .icon { + font-size: 20px; +} + +/* BANGS */ +.bangs-grid { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.bang { + padding: 6px 10px; + border: 1px solid var(--border-thin-color); + font-size: 10px; + cursor: pointer; + transition: all 0.1s; +} + +.bang:hover { + background: var(--fg); + color: var(--bg); +} + +/* CALCULATOR */ +.calc-display { + background: var(--hover-bg); + padding: 15px; + margin-bottom: 15px; + border: 1px solid var(--border-thin-color); +} + +.calc-expr { + font-size: 12px; + color: var(--gray); + min-height: 16px; +} + +.calc-result { + font-size: 28px; + font-weight: 700; +} + +.calc-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 4px; +} + +.calc-btn { + padding: 12px; + border: 1px solid var(--border-thin-color); + background: transparent; + color: var(--fg); + font-family: inherit; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.1s; +} + +.calc-btn:hover { + background: var(--fg); + color: var(--bg); +} + +.calc-btn.op { + background: var(--hover-bg); +} + +/* CLOCKS */ +.clocks-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; +} + +.clock { + padding: 12px; + border: 1px solid var(--border-thin-color); + text-align: center; +} + +.clock-time { + font-size: 16px; + font-weight: 700; + font-variant-numeric: tabular-nums; +} + +.clock-label { + font-size: 8px; + text-transform: uppercase; + letter-spacing: 2px; + color: var(--gray); + margin-top: 4px; +} + +/* HISTORY */ +.history-item { + padding: 10px 0; + border-bottom: 1px solid var(--border-thin-color); + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + cursor: pointer; + color: var(--gray); +} + +.history-item:hover { + color: var(--fg); +} + +.history-item:last-child { + border-bottom: none; +} + +.history-time { + font-size: 9px; +} + +/* SOURCES LIST */ +.sources-list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.source-item { + padding: 10px 12px; + border: 1px solid var(--border-thin-color); + display: flex; + justify-content: space-between; + align-items: center; + font-size: 10px; + letter-spacing: 1px; + cursor: pointer; + transition: all 0.1s; +} + +.source-item:hover { + border-color: var(--fg); +} + +.source-item.active { + background: var(--fg); + color: var(--bg); +} + +.source-count { + font-weight: 700; +} + +/* WATCHLIST (Editable) */ +.watchlist-input-row { + display: flex; + margin-bottom: 10px; + border: 1px solid var(--border-thin-color); +} + +#watchlist-input { + flex: 1; + background: transparent; + border: none; + color: var(--fg); + font-family: inherit; + font-size: 10px; + padding: 8px; + outline: none; + text-transform: uppercase; +} + +#watchlist-add-btn { + padding: 0 10px; + background: var(--hover-bg); + border: none; + border-left: 1px solid var(--border-thin-color); + color: var(--fg); + cursor: pointer; + font-size: 14px; +} + +#watchlist-add-btn:hover { + background: var(--fg); + color: var(--bg); +} + +.watchlist-item-row { + padding: 10px 12px; + border: 1px solid var(--border-thin-color); + display: flex; + justify-content: space-between; + align-items: center; + font-size: 10px; + letter-spacing: 1px; + cursor: pointer; + transition: all 0.1s; + margin-bottom: 4px; +} + +.watchlist-item-row:hover { + border-color: var(--fg); +} + +.watchlist-del-btn { + color: var(--gray); + padding: 0 5px; + cursor: pointer; + font-size: 14px; +} + +.watchlist-del-btn:hover { + color: var(--fg); + font-weight: bold; +} + +/* RESULTS */ +.results-section { + min-height: 400px; +} + +.results-header { + display: flex; + justify-content: space-between; + padding: 15px 25px; + border-bottom: var(--border-thin); + font-size: 10px; + text-transform: uppercase; + letter-spacing: 2px; + color: var(--gray); +} + +.results-container { + display: flex; + flex-direction: column; +} + +/* STOCK WIDGET */ +.stock-widget { + border-bottom: var(--border); + background: var(--panel); +} + +.stock-header { + padding: 30px; + border-bottom: var(--border-thin); + display: flex; + justify-content: space-between; + align-items: center; +} + +.stock-info h2 { + font-size: 32px; + font-weight: 700; + letter-spacing: -1px; +} + +.stock-info .ticker { + font-size: 12px; + color: var(--gray); + letter-spacing: 2px; + margin-top: 5px; +} + +.stock-actions-top { + display: flex; + gap: 10px; +} + +.stock-btn { + padding: 10px 20px; + border: 2px solid var(--border-thin-color); + background: transparent; + color: var(--fg); + font-family: inherit; + font-size: 10px; + letter-spacing: 1px; + cursor: pointer; + transition: all 0.1s; +} + +.stock-btn:hover { + background: var(--fg); + color: var(--bg); + border-color: var(--fg); +} + +.tradingview-widget-container { + height: 500px; +} + +/* RESULT CARD */ +.result { + border-bottom: var(--border-thin); + display: grid; + grid-template-columns: 50px 1fr; + transition: background 0.1s; +} + +.result:hover { + background: var(--hover-bg); +} + +.result-index { + padding: 20px 15px; + border-right: var(--border-thin); + font-size: 11px; + color: var(--gray); + text-align: center; + font-weight: 700; +} + +.result-body { + padding: 20px 25px; +} + +.result-source { + font-size: 9px; + text-transform: uppercase; + letter-spacing: 2px; + color: var(--gray); + margin-bottom: 8px; +} + +.result-title { + font-size: 16px; + font-weight: 600; + letter-spacing: -0.5px; + margin-bottom: 8px; + line-height: 1.4; + cursor: pointer; +} + +.result-title:hover { + text-decoration: underline; + text-underline-offset: 3px; +} + +.result-url { + font-size: 11px; + color: var(--gray); + margin-bottom: 10px; + word-break: break-all; +} + +.result-snippet { + font-size: 12px; + line-height: 1.7; + color: var(--gray); +} + +.result-snippet mark { + background: var(--fg); + color: var(--bg); + padding: 0 2px; +} + +.result-meta { + display: flex; + gap: 15px; + margin-top: 12px; + font-size: 10px; + color: var(--gray); + text-transform: uppercase; + letter-spacing: 1px; +} + +/* INSTANT ANSWER */ +.instant-box { + border-bottom: var(--border); + padding: 40px; + background: var(--panel); +} + +.instant-label { + font-size: 9px; + text-transform: uppercase; + letter-spacing: 3px; + color: var(--gray); + margin-bottom: 20px; + display: flex; + align-items: center; + gap: 10px; +} + +.instant-label::before { + content: "■"; + animation: blink 0.5s infinite; +} + +.instant-title { + font-size: 28px; + font-weight: 700; + letter-spacing: -1px; + margin-bottom: 20px; + line-height: 1.2; +} + +.instant-content { + font-size: 14px; + line-height: 1.8; + color: var(--gray); + max-width: 700px; +} + +.instant-content p { + margin-bottom: 12px; +} + +.instant-meta { + margin-top: 25px; + padding-top: 20px; + border-top: var(--border-thin); + display: flex; + gap: 25px; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + color: var(--gray); +} + +.instant-meta a { + border-bottom: 1px solid var(--gray); +} + +.instant-meta a:hover { + border-color: var(--fg); + color: var(--fg); +} + +/* EMPTY STATE */ +.empty-state { + padding: 60px 30px; + text-align: center; +} + +.empty-title { + font-size: 18px; + font-weight: 700; + margin-bottom: 15px; + color: var(--gray); +} + +.empty-text { + color: var(--gray); + font-size: 12px; + line-height: 1.8; +} + +/* LOADING */ +.loading-row { + display: flex; + padding: 25px; + border-bottom: var(--border-thin); + gap: 20px; + align-items: center; +} + +.loading-bar { + height: 2px; + background: var(--hover-bg); + flex: 1; + position: relative; + overflow: hidden; +} + +.loading-bar::after { + content: ""; + position: absolute; + left: -50%; + width: 50%; + height: 100%; + background: var(--fg); + animation: loading 1s infinite; +} + +@keyframes loading { + to { + left: 150%; + } +} + +.loading-text { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 2px; + color: var(--gray); +} + +/* TOOL MODAL */ +.tool-modal { + padding: 30px; + background: var(--panel); +} + +.tool-modal textarea { + width: 100%; + padding: 15px; + background: var(--bg); + border: 2px solid var(--border-thin-color); + color: var(--fg); + font-family: inherit; + font-size: 13px; + resize: vertical; + min-height: 100px; + margin: 15px 0; +} + +.tool-modal textarea:focus { + outline: none; + border-color: var(--fg); +} + +.tool-btn-row { + display: flex; + gap: 10px; +} + +.tool-action { + padding: 12px 25px; + border: 2px solid var(--border-thin-color); + background: transparent; + color: var(--fg); + font-family: inherit; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 2px; + cursor: pointer; +} + +.tool-action:hover { + background: var(--fg); + color: var(--bg); +} + +.tool-output { + margin-top: 20px; + padding: 20px; + background: var(--bg); + border: 2px solid var(--border-thin-color); + font-size: 12px; + word-break: break-all; +} + +/* TOAST */ +.toast-container { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 3000; + display: flex; + flex-direction: column; + gap: 10px; +} + +.toast { + padding: 15px 30px; + border: 3px solid var(--fg); + background: var(--bg); + font-size: 12px; + letter-spacing: 2px; + animation: toastIn 0.2s ease; +} + +@keyframes toastIn { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* FOOTER */ +.footer { + border-top: var(--border); + padding: 20px 30px; + display: flex; + justify-content: center; + align-items: center; + gap: 40px; + font-size: 10px; + color: var(--gray); + text-transform: uppercase; + letter-spacing: 2px; +} + +.theme-btn { + background: transparent; + border: 1px solid var(--gray); + color: var(--gray); + padding: 5px 10px; + cursor: pointer; + font-family: inherit; + text-transform: uppercase; + letter-spacing: 1px; + font-size: 9px; +} + +.theme-btn:hover { + border-color: var(--fg); + color: var(--fg); +} + +/* SCROLLBAR */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg); +} + +::-webkit-scrollbar-thumb { + background: #222; +} + +body.light-mode ::-webkit-scrollbar-thumb { + background: #ccc; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--gray); +} + +/* RESPONSIVE */ +@media (max-width: 768px) { + .logo-large { + font-size: 48px; + } + .hero.compact .logo-large { + display: none; + } + #search-input { + font-size: 18px; + padding: 20px; + } + .search-prefix { + padding: 20px; + font-size: 20px; + } + .main { + grid-template-columns: 1fr; + } +} diff --git a/style.css b/style.css deleted file mode 100644 index 6f3f24e..0000000 --- a/style.css +++ /dev/null @@ -1,1022 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap"); - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -:root { - /* DARK MODE (DEFAULT) */ - --bg: #000000; - --fg: #ffffff; - --gray: #666666; - --panel: #0a0a0a; - --border-color: #ffffff; - --border-thin-color: #333333; - --input-ph: #333333; - --hover-bg: #111111; -} - -body.light-mode { - /* LIGHT MODE (INVERTED) */ - --bg: #f4f4f5; - --fg: #09090b; - --gray: #52525b; - --panel: #e4e4e7; - --border-color: #09090b; - --border-thin-color: #a1a1aa; - --input-ph: #a1a1aa; - --hover-bg: #d4d4d8; -} - -/* Variables used in CSS */ -:root { - --border: 3px solid var(--border-color); - --border-thin: 1px solid var(--border-thin-color); -} - -::selection { - background: var(--fg); - color: var(--bg); -} - -html { - font-size: 14px; -} - -body { - font-family: "IBM Plex Mono", monospace; - background: var(--bg); - color: var(--fg); - min-height: 100vh; - cursor: crosshair; - transition: - background 0.2s, - color 0.2s; -} - -a { - color: inherit; - text-decoration: none; -} - -/* HERO SEARCH */ -.hero { - min-height: 50vh; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - padding: 40px 20px; - border-bottom: var(--border); - position: relative; -} - -.hero.compact { - min-height: auto; - padding: 20px; - position: sticky; - top: 0; - background: var(--bg); - z-index: 1000; -} - -.logo-large { - font-size: 72px; - font-weight: 700; - letter-spacing: -4px; - margin-bottom: 10px; - display: flex; - align-items: center; - gap: 10px; -} - -.hero.compact .logo-large { - font-size: 24px; - letter-spacing: -1px; - margin-bottom: 0; - position: absolute; - left: 30px; - top: 50%; - transform: translateY(-50%); -} - -.logo-large::before { - content: ">"; - animation: blink 1s infinite; -} - -@keyframes blink { - 0%, - 49% { - opacity: 1; - } - 50%, - 100% { - opacity: 0; - } -} - -.tagline { - font-size: 12px; - color: var(--gray); - letter-spacing: 4px; - text-transform: uppercase; - margin-bottom: 50px; -} - -.hero.compact .tagline { - display: none; -} - -.search-container { - width: 100%; - max-width: 800px; -} - -.search-box { - display: flex; - border: var(--border); - background: var(--bg); -} - -.search-prefix { - padding: 25px 25px; - font-size: 28px; - font-weight: 700; - border-right: var(--border); - background: var(--fg); - color: var(--bg); -} - -#search-input { - flex: 1; - padding: 25px 30px; - font-size: 24px; - font-family: inherit; - font-weight: 600; - background: transparent; - color: var(--fg); - border: none; - outline: none; - letter-spacing: -0.5px; -} - -#search-input::placeholder { - color: var(--input-ph); -} - -.search-actions { - display: flex; -} - -.action-btn { - width: 80px; - background: transparent; - border: none; - border-left: var(--border); - color: var(--fg); - font-size: 20px; - cursor: pointer; - transition: all 0.1s; - font-family: inherit; -} - -.action-btn:hover { - background: var(--fg); - color: var(--bg); -} - -.hero.compact .search-container { - max-width: 600px; -} - -.hero.compact .search-box { - border-width: 2px; -} - -.hero.compact .search-prefix { - padding: 15px 20px; - font-size: 20px; - border-width: 2px; -} - -.hero.compact #search-input { - padding: 15px 20px; - font-size: 18px; -} - -.hero.compact .action-btn { - width: 60px; - border-width: 2px; -} - -/* SHORTCUTS */ -.shortcuts { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 10px; - margin-top: 30px; -} - -.hero.compact .shortcuts { - display: none; -} - -.shortcut { - padding: 12px 20px; - border: 2px solid var(--border-thin-color); - font-size: 11px; - letter-spacing: 2px; - cursor: pointer; - transition: all 0.1s; - text-transform: uppercase; -} - -.shortcut:hover { - border-color: var(--fg); - background: var(--fg); - color: var(--bg); -} - -/* STATS BAR */ -.stats-bar { - display: none; - justify-content: center; - gap: 40px; - padding: 15px; - font-size: 11px; - letter-spacing: 2px; - color: var(--gray); - border-bottom: var(--border-thin); -} - -.stats-bar.visible { - display: flex; -} - -.stat-item { - display: flex; - gap: 10px; -} - -.stat-value { - color: var(--fg); - font-weight: 700; -} - -/* MAIN LAYOUT */ -.main { - display: grid; - grid-template-columns: 300px 1fr 300px; - min-height: 50vh; -} - -@media (max-width: 1200px) { - .main { - grid-template-columns: 1fr; - } - .sidebar-left, - .sidebar-right { - border: none !important; - border-bottom: var(--border) !important; - } -} - -/* SIDEBARS */ -.sidebar-left, -.sidebar-right { - border-right: var(--border); -} - -.sidebar-right { - border-right: none; - border-left: var(--border); -} - -/* WIDGET */ -.widget { - border-bottom: var(--border-thin); -} - -.widget-header { - padding: 15px 20px; - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; - transition: background 0.1s; -} - -.widget-header:hover { - background: var(--hover-bg); -} - -.widget-title { - font-size: 10px; - text-transform: uppercase; - letter-spacing: 3px; - color: var(--gray); -} - -.widget-toggle { - font-size: 16px; - color: var(--gray); - transition: transform 0.2s; -} - -.widget.open .widget-toggle { - transform: rotate(45deg); -} - -.widget-body { - display: none; - padding: 20px; - border-top: var(--border-thin); -} - -.widget.open .widget-body { - display: block; -} - -/* TOOLS GRID */ -.tools-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 8px; -} - -.tool-btn { - padding: 15px 10px; - border: 2px solid var(--border-thin-color); - background: transparent; - color: var(--gray); - font-family: inherit; - font-size: 9px; - text-transform: uppercase; - letter-spacing: 1px; - cursor: pointer; - transition: all 0.1s; - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -} - -.tool-btn:hover { - background: var(--fg); - color: var(--bg); - border-color: var(--fg); -} - -.tool-btn .icon { - font-size: 20px; -} - -/* BANGS */ -.bangs-grid { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.bang { - padding: 6px 10px; - border: 1px solid var(--border-thin-color); - font-size: 10px; - cursor: pointer; - transition: all 0.1s; -} - -.bang:hover { - background: var(--fg); - color: var(--bg); -} - -/* CALCULATOR */ -.calc-display { - background: var(--hover-bg); - padding: 15px; - margin-bottom: 15px; - border: 1px solid var(--border-thin-color); -} - -.calc-expr { - font-size: 12px; - color: var(--gray); - min-height: 16px; -} - -.calc-result { - font-size: 28px; - font-weight: 700; -} - -.calc-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 4px; -} - -.calc-btn { - padding: 12px; - border: 1px solid var(--border-thin-color); - background: transparent; - color: var(--fg); - font-family: inherit; - font-size: 14px; - font-weight: 600; - cursor: pointer; - transition: all 0.1s; -} - -.calc-btn:hover { - background: var(--fg); - color: var(--bg); -} - -.calc-btn.op { - background: var(--hover-bg); -} - -/* CLOCKS */ -.clocks-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 10px; -} - -.clock { - padding: 12px; - border: 1px solid var(--border-thin-color); - text-align: center; -} - -.clock-time { - font-size: 16px; - font-weight: 700; - font-variant-numeric: tabular-nums; -} - -.clock-label { - font-size: 8px; - text-transform: uppercase; - letter-spacing: 2px; - color: var(--gray); - margin-top: 4px; -} - -/* HISTORY */ -.history-item { - padding: 10px 0; - border-bottom: 1px solid var(--border-thin-color); - display: flex; - justify-content: space-between; - align-items: center; - font-size: 12px; - cursor: pointer; - color: var(--gray); -} - -.history-item:hover { - color: var(--fg); -} - -.history-item:last-child { - border-bottom: none; -} - -.history-time { - font-size: 9px; -} - -/* SOURCES LIST */ -.sources-list { - display: flex; - flex-direction: column; - gap: 4px; -} - -.source-item { - padding: 10px 12px; - border: 1px solid var(--border-thin-color); - display: flex; - justify-content: space-between; - align-items: center; - font-size: 10px; - letter-spacing: 1px; - cursor: pointer; - transition: all 0.1s; -} - -.source-item:hover { - border-color: var(--fg); -} - -.source-item.active { - background: var(--fg); - color: var(--bg); -} - -.source-count { - font-weight: 700; -} - -/* WATCHLIST (Editable) */ -.watchlist-input-row { - display: flex; - margin-bottom: 10px; - border: 1px solid var(--border-thin-color); -} - -#watchlist-input { - flex: 1; - background: transparent; - border: none; - color: var(--fg); - font-family: inherit; - font-size: 10px; - padding: 8px; - outline: none; - text-transform: uppercase; -} - -#watchlist-add-btn { - padding: 0 10px; - background: var(--hover-bg); - border: none; - border-left: 1px solid var(--border-thin-color); - color: var(--fg); - cursor: pointer; - font-size: 14px; -} - -#watchlist-add-btn:hover { - background: var(--fg); - color: var(--bg); -} - -.watchlist-item-row { - padding: 10px 12px; - border: 1px solid var(--border-thin-color); - display: flex; - justify-content: space-between; - align-items: center; - font-size: 10px; - letter-spacing: 1px; - cursor: pointer; - transition: all 0.1s; - margin-bottom: 4px; -} - -.watchlist-item-row:hover { - border-color: var(--fg); -} - -.watchlist-del-btn { - color: var(--gray); - padding: 0 5px; - cursor: pointer; - font-size: 14px; -} - -.watchlist-del-btn:hover { - color: var(--fg); - font-weight: bold; -} - -/* RESULTS */ -.results-section { - min-height: 400px; -} - -.results-header { - display: flex; - justify-content: space-between; - padding: 15px 25px; - border-bottom: var(--border-thin); - font-size: 10px; - text-transform: uppercase; - letter-spacing: 2px; - color: var(--gray); -} - -.results-container { - display: flex; - flex-direction: column; -} - -/* STOCK WIDGET */ -.stock-widget { - border-bottom: var(--border); - background: var(--panel); -} - -.stock-header { - padding: 30px; - border-bottom: var(--border-thin); - display: flex; - justify-content: space-between; - align-items: center; -} - -.stock-info h2 { - font-size: 32px; - font-weight: 700; - letter-spacing: -1px; -} - -.stock-info .ticker { - font-size: 12px; - color: var(--gray); - letter-spacing: 2px; - margin-top: 5px; -} - -.stock-actions-top { - display: flex; - gap: 10px; -} - -.stock-btn { - padding: 10px 20px; - border: 2px solid var(--border-thin-color); - background: transparent; - color: var(--fg); - font-family: inherit; - font-size: 10px; - letter-spacing: 1px; - cursor: pointer; - transition: all 0.1s; -} - -.stock-btn:hover { - background: var(--fg); - color: var(--bg); - border-color: var(--fg); -} - -.tradingview-widget-container { - height: 500px; -} - -/* RESULT CARD */ -.result { - border-bottom: var(--border-thin); - display: grid; - grid-template-columns: 50px 1fr; - transition: background 0.1s; -} - -.result:hover { - background: var(--hover-bg); -} - -.result-index { - padding: 20px 15px; - border-right: var(--border-thin); - font-size: 11px; - color: var(--gray); - text-align: center; - font-weight: 700; -} - -.result-body { - padding: 20px 25px; -} - -.result-source { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 2px; - color: var(--gray); - margin-bottom: 8px; -} - -.result-title { - font-size: 16px; - font-weight: 600; - letter-spacing: -0.5px; - margin-bottom: 8px; - line-height: 1.4; - cursor: pointer; -} - -.result-title:hover { - text-decoration: underline; - text-underline-offset: 3px; -} - -.result-url { - font-size: 11px; - color: var(--gray); - margin-bottom: 10px; - word-break: break-all; -} - -.result-snippet { - font-size: 12px; - line-height: 1.7; - color: var(--gray); -} - -.result-snippet mark { - background: var(--fg); - color: var(--bg); - padding: 0 2px; -} - -.result-meta { - display: flex; - gap: 15px; - margin-top: 12px; - font-size: 10px; - color: var(--gray); - text-transform: uppercase; - letter-spacing: 1px; -} - -/* INSTANT ANSWER */ -.instant-box { - border-bottom: var(--border); - padding: 40px; - background: var(--panel); -} - -.instant-label { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 3px; - color: var(--gray); - margin-bottom: 20px; - display: flex; - align-items: center; - gap: 10px; -} - -.instant-label::before { - content: "■"; - animation: blink 0.5s infinite; -} - -.instant-title { - font-size: 28px; - font-weight: 700; - letter-spacing: -1px; - margin-bottom: 20px; - line-height: 1.2; -} - -.instant-content { - font-size: 14px; - line-height: 1.8; - color: var(--gray); - max-width: 700px; -} - -.instant-content p { - margin-bottom: 12px; -} - -.instant-meta { - margin-top: 25px; - padding-top: 20px; - border-top: var(--border-thin); - display: flex; - gap: 25px; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 1px; - color: var(--gray); -} - -.instant-meta a { - border-bottom: 1px solid var(--gray); -} - -.instant-meta a:hover { - border-color: var(--fg); - color: var(--fg); -} - -/* EMPTY STATE */ -.empty-state { - padding: 60px 30px; - text-align: center; -} - -.empty-title { - font-size: 18px; - font-weight: 700; - margin-bottom: 15px; - color: var(--gray); -} - -.empty-text { - color: var(--gray); - font-size: 12px; - line-height: 1.8; -} - -/* LOADING */ -.loading-row { - display: flex; - padding: 25px; - border-bottom: var(--border-thin); - gap: 20px; - align-items: center; -} - -.loading-bar { - height: 2px; - background: var(--hover-bg); - flex: 1; - position: relative; - overflow: hidden; -} - -.loading-bar::after { - content: ""; - position: absolute; - left: -50%; - width: 50%; - height: 100%; - background: var(--fg); - animation: loading 1s infinite; -} - -@keyframes loading { - to { - left: 150%; - } -} - -.loading-text { - font-size: 10px; - text-transform: uppercase; - letter-spacing: 2px; - color: var(--gray); -} - -/* TOOL MODAL */ -.tool-modal { - padding: 30px; - background: var(--panel); -} - -.tool-modal textarea { - width: 100%; - padding: 15px; - background: var(--bg); - border: 2px solid var(--border-thin-color); - color: var(--fg); - font-family: inherit; - font-size: 13px; - resize: vertical; - min-height: 100px; - margin: 15px 0; -} - -.tool-modal textarea:focus { - outline: none; - border-color: var(--fg); -} - -.tool-btn-row { - display: flex; - gap: 10px; -} - -.tool-action { - padding: 12px 25px; - border: 2px solid var(--border-thin-color); - background: transparent; - color: var(--fg); - font-family: inherit; - font-size: 11px; - text-transform: uppercase; - letter-spacing: 2px; - cursor: pointer; -} - -.tool-action:hover { - background: var(--fg); - color: var(--bg); -} - -.tool-output { - margin-top: 20px; - padding: 20px; - background: var(--bg); - border: 2px solid var(--border-thin-color); - font-size: 12px; - word-break: break-all; -} - -/* TOAST */ -.toast-container { - position: fixed; - bottom: 20px; - left: 50%; - transform: translateX(-50%); - z-index: 3000; - display: flex; - flex-direction: column; - gap: 10px; -} - -.toast { - padding: 15px 30px; - border: 3px solid var(--fg); - background: var(--bg); - font-size: 12px; - letter-spacing: 2px; - animation: toastIn 0.2s ease; -} - -@keyframes toastIn { - from { - transform: translateY(20px); - opacity: 0; - } - to { - transform: translateY(0); - opacity: 1; - } -} - -/* FOOTER */ -.footer { - border-top: var(--border); - padding: 20px 30px; - display: flex; - justify-content: center; - align-items: center; - gap: 40px; - font-size: 10px; - color: var(--gray); - text-transform: uppercase; - letter-spacing: 2px; -} - -.theme-btn { - background: transparent; - border: 1px solid var(--gray); - color: var(--gray); - padding: 5px 10px; - cursor: pointer; - font-family: inherit; - text-transform: uppercase; - letter-spacing: 1px; - font-size: 9px; -} - -.theme-btn:hover { - border-color: var(--fg); - color: var(--fg); -} - -/* SCROLLBAR */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: var(--bg); -} - -::-webkit-scrollbar-thumb { - background: #222; -} - -body.light-mode ::-webkit-scrollbar-thumb { - background: #ccc; -} - -::-webkit-scrollbar-thumb:hover { - background: var(--gray); -} - -/* RESPONSIVE */ -@media (max-width: 768px) { - .logo-large { - font-size: 48px; - } - .hero.compact .logo-large { - display: none; - } - #search-input { - font-size: 18px; - padding: 20px; - } - .search-prefix { - padding: 20px; - font-size: 20px; - } - .main { - grid-template-columns: 1fr; - } -} -- cgit v1.2.3-70-g09d2