From 91802c21d446175a02c820bcd243920a0791441b Mon Sep 17 00:00:00 2001 From: Petri Hienonen Date: Sun, 14 Dec 2025 11:22:29 +0200 Subject: Intial commit --- biome.json | 100 ++++++ flake.lock | 27 ++ flake.nix | 28 ++ index.html | 264 ++++++++++++++++ main.js | 972 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 1022 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 2413 insertions(+) create mode 100644 biome.json create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 index.html create mode 100644 main.js create mode 100644 style.css diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..7463a70 --- /dev/null +++ b/biome.json @@ -0,0 +1,100 @@ +{ + "assist": { + "actions": { + "source": { + "organizeImports": "on", + "recommended": true, + "useSortedAttributes": "on", + "useSortedKeys": "on" + } + }, + "enabled": true + }, + "files": { + "ignoreUnknown": true, + "includes": ["index.html", "style.css", "main.js", "biome.json"] + }, + "formatter": { + "indentStyle": "tab", + "lineWidth": 100 + }, + "html": { + "experimentalFullSupportEnabled": true, + "formatter": { + "enabled": true, + "indentScriptAndStyle": true, + "lineWidth": 100, + "whitespaceSensitivity": "ignore" + }, + "linter": { "enabled": true } + }, + "linter": { + "enabled": true, + "rules": { + "complexity": { + "noExcessiveCognitiveComplexity": { + "level": "warn", + "options": { + "maxAllowedComplexity": 50 + } + } + }, + "correctness": { + "noUnusedVariables": { + "fix": "none", + "level": "warn" + } + }, + "nursery": { + "recommended": true + }, + "recommended": true, + "style": { + "useNamingConvention": { + "level": "error", + "options": { + "conventions": [ + { + "formats": ["PascalCase"], + "selector": { + "kind": "typeParameter" + } + }, + { + "formats": ["camelCase"], + "selector": { + "kind": "let" + } + }, + { + "formats": ["camelCase"], + "selector": { + "kind": "classMember" + } + }, + { + "formats": ["camelCase"], + "selector": { + "kind": "classProperty" + } + }, + { + "formats": ["camelCase"], + "selector": { + "kind": "function" + } + }, + { + "formats": ["camelCase", "PascalCase"], + "selector": { + "kind": "objectLiteralMember" + } + } + ], + "strictCase": false + } + } + } + } + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7d39aeb --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1765311797, + "narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..2cf8f59 --- /dev/null +++ b/flake.nix @@ -0,0 +1,28 @@ +{ + description = "Page"; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; + }; + + outputs = + { self, nixpkgs }: + let + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + ]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); + in + { + devShells = forAllSystems ( + system: + let + pkgs = nixpkgsFor.${system}; + in + { + default = pkgs.mkShell { buildInputs = with pkgs; [ biome ]; }; + } + ); + }; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..5573e60 --- /dev/null +++ b/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/main.js b/main.js new file mode 100644 index 0000000..8bba38e --- /dev/null +++ b/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/style.css b/style.css new file mode 100644 index 0000000..6f3f24e --- /dev/null +++ b/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 !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