您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Advanced Study Boost: keyword scan, accordion links, per-site mute, dark-mode, custom keywords, daily quiz, highlight, rescan. Panel remains open until user minimizes. Responsive/mobile-friendly.
// ==UserScript== // @name Syllabus MCQ Helper — Advanced (UPSC / SSC CGL) — Panel stays open fix // @namespace https://example.com/tm/syllabus-mcq-helper-advanced // @version 1.0.1 // @license MIT // @description Advanced Study Boost: keyword scan, accordion links, per-site mute, dark-mode, custom keywords, daily quiz, highlight, rescan. Panel remains open until user minimizes. Responsive/mobile-friendly. // @author iamnobodybaba // @match *://*/* // @grant GM_registerMenuCommand // @run-at document-end // ==/UserScript== /* Minimal inline comments. Key change: panel open state persisted to sessionStorage (key 'smhq_panel_open') so re-rendering does not force the panel to collapse. */ (function () { "use strict"; /* ---------- Storage keys & util ---------- */ const LS = { EXAMS: "smhq_exams", LANG: "smhq_lang", MUTED: "smhq_muted_sites", CUSTOM_KW: "smhq_custom_keywords", THEME: "smhq_theme_pref", DAILY_DATE: "smhq_daily_date", DAILY_SEED: "smhq_daily_seed", HIGHLIGHT: "smhq_highlight_enabled", SAVED: "smhq_saved_links" }; const PANEL_OPEN_KEY = "smhq_panel_open"; // sessionStorage key to persist panel open state (per-tab/session) function lsGet(key, fallback = null) { try { const v = localStorage.getItem(key); return v ? JSON.parse(v) : fallback; } catch { return fallback; } } function lsSet(key, val) { localStorage.setItem(key, JSON.stringify(val)); } const currentHost = location.hostname; /* ---------- Base syllabus & i18n ---------- */ const baseSyllabus = { polity: { keywords: ["constitution", "fundamental rights", "parliament", "president", "governor", "preamble", "judiciary"], links: [ { name: "GKToday Polity MCQs", url: "https://www.gktoday.in/quiz-questions-answers/indian-polity-constitution/" }, { name: "Drishti IAS Polity Quiz", url: "https://www.drishtiias.com/quiz" }, { name: "ClearIAS Polity Test", url: "https://www.clearias.com/upsc-prelims-online-mock-tests/" } ] }, economy: { keywords: ["budget", "fiscal deficit", "inflation", "rbi", "gst", "wto", "balance of payments"], links: [ { name: "GKToday Economy MCQs", url: "https://www.gktoday.in/quiz-questions-answers/indian-economy/" }, { name: "AffairsCloud Economy Quiz", url: "https://affairscloud.com/quiz-section/" }, { name: "Examrace Economy MCQs", url: "https://www.examrace.com/Current-Affairs/MCQs/" } ] }, history: { keywords: ["indus valley", "maurya", "gupta", "1857", "gandhi", "nehru", "quit india"], links: [ { name: "GKToday History MCQs", url: "https://www.gktoday.in/quiz-questions-answers/ancient-medieval-history/" }, { name: "JagranJosh History Quiz", url: "https://www.jagranjosh.com/general-knowledge-quiz" }, { name: "Drishti IAS History Quiz", url: "https://www.drishtiias.com/quiz" } ] }, geography: { keywords: ["himalayas", "monsoon", "soil", "climate", "river", "el nino", "plate tectonics"], links: [ { name: "GKToday Geography MCQs", url: "https://www.gktoday.in/quiz-questions-answers/indian-geography/" }, { name: "ClearIAS Geography Quiz", url: "https://www.clearias.com/upsc-prelims-online-mock-tests/" }, { name: "IASbaba Daily Quiz", url: "https://iasbaba.com/iasbaba-daily-prelims-test/" } ] }, science: { keywords: ["photosynthesis", "genetics", "gravity", "satellite", "nuclear", "vaccine", "disease"], links: [ { name: "GKToday Science MCQs", url: "https://www.gktoday.in/quiz-questions-answers/general-science/" }, { name: "AffairsCloud Science Quiz", url: "https://affairscloud.com/quiz/" }, { name: "Examrace Science MCQs", url: "https://www.examrace.com/Current-Affairs/MCQs/" } ] }, current: { keywords: ["g20", "brics", "un", "climate change", "imf", "world bank", "wto", "summit"], links: [ { name: "AffairsCloud Current Affairs Quiz", url: "https://affairscloud.com/quiz/" }, { name: "GKToday Current Affairs MCQs", url: "https://www.gktoday.in/current-affairs-quiz-questions-answers/" }, { name: "IASbaba Daily Quiz", url: "https://iasbaba.com/iasbaba-daily-prelims-test/" } ] } }; const SUBJECT_TITLES = { polity: { en: "Polity", hi: "राजनीति" }, economy: { en: "Economy", hi: "अर्थशास्त्र" }, history: { en: "History", hi: "इतिहास" }, geography: { en: "Geography", hi: "भूगोल" }, science: { en: "Science", hi: "विज्ञान" }, current: { en: "Current Affairs", hi: "सामयिक" } }; const STR = { en: { title: "Study Boost", selectExamsTitle: "Select your exam(s)", save: "Save", todaysQuiz: "Today's Quiz", rescan: "Rescan", settings: "Settings", muteSite: "Don't show on this site", minimize: "Minimize", expand: "Expand", addKeyword: "Add keyword", importExport: "Import / Export JSON", highlightToggle: "Highlight keywords on page", language: "Language", theme: "Theme", themeAuto: "Auto", themeLight: "Light", themeDark: "Dark", customKeywords: "Custom Keywords", importPlaceholder: "Paste JSON here to import or press Export to copy current config.", export: "Export", importBtn: "Import", savedLinks: "Saved Links", copyLink: "Copy link", bookmark: "Save", clearAll: "Clear All", atLeastOne: "Please select at least one exam." }, hi: { title: "अध्ययन बूस्ट", selectExamsTitle: "अपने परीक्षा(यों) को चुनें", save: "सेव करें", todaysQuiz: "आज की क्विज़", rescan: "पुनः स्कैन करें", settings: "सेटिंग्स", muteSite: "इस साइट पर दिखाएँ नहीं", minimize: "छोटा करें", expand: "बड़ा करें", addKeyword: "कीवर्ड जोड़ें", importExport: "इम्पोर्ट / एक्सपोर्ट JSON", highlightToggle: "पेज पर कीवर्ड हाइलाइट करें", language: "भाषा", theme: "थीम", themeAuto: "ऑटो", themeLight: "लाइट", themeDark: "डार्क", customKeywords: "कस्टम कीवर्ड्स", importPlaceholder: "इम्पोर्ट करने के लिए JSON यहाँ पेस्ट करें या करेंट कॉन्फ़िग कॉपी करने के लिए Export दबाएँ।", export: "एक्सपोर्ट", importBtn: "इम्पोर्ट", savedLinks: "सहेजे हुए लिंक", copyLink: "लिंक कॉपी करें", bookmark: "सहेजें", clearAll: "सभी हटाएँ", atLeastOne: "कृपया कम से कम एक परीक्षा चुनें।" } }; /* ---------- Pref helpers ---------- */ function getExams() { const e = lsGet(LS.EXAMS, null); return Array.isArray(e) && e.length ? e : null; } function setExams(a) { lsSet(LS.EXAMS, a); } function getLang() { return lsGet(LS.LANG, "en"); } function setLang(v) { lsSet(LS.LANG, v); } function isMutedHere() { const m = lsGet(LS.MUTED, {}); return !!m[currentHost]; } function setMutedHere(val) { const m = lsGet(LS.MUTED, {}); if (val) m[currentHost] = true; else delete m[currentHost]; lsSet(LS.MUTED, m); } function getCustomKeywords() { return lsGet(LS.CUSTOM_KW, {}); } function setCustomKeywords(o) { lsSet(LS.CUSTOM_KW, o); } function getThemePref() { return lsGet(LS.THEME, "auto"); } function setThemePref(v) { lsSet(LS.THEME, v); } function getHighlightPref() { return !!lsGet(LS.HIGHLIGHT, true); } function setHighlightPref(v) { lsSet(LS.HIGHLIGHT, !!v); } function getSavedLinks() { return lsGet(LS.SAVED, []); } function setSavedLinks(a) { lsSet(LS.SAVED, a); } function buildSyllabus() { const s = JSON.parse(JSON.stringify(baseSyllabus)); const custom = getCustomKeywords() || {}; for (const [sub, kws] of Object.entries(custom)) { if (!s[sub]) s[sub] = { keywords: [], links: [] }; for (const k of kws) if (!s[sub].keywords.includes(k)) s[sub].keywords.push(k); } return s; } /* ---------- UI CSS ---------- */ function injectStyles() { if (document.getElementById("smhq-styles")) return; const style = document.createElement("style"); style.id = "smhq-styles"; style.textContent = ` :root { --smhq-bg: #ffffff; --smhq-fg: #111; --smhq-accent:#0b5ed7; --smhq-hl-bg: #fff3bf; --smhq-hl-fg: #111; } @media (prefers-color-scheme: dark) { :root { --smhq-bg: #111316; --smhq-fg: #e6eef8; --smhq-accent: #558bff; --smhq-hl-bg: #3a3b3c; --smhq-hl-fg: #fff; } } .smhq-wrap { position: fixed; bottom: 12px; right: 12px; z-index:2147483647; font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial; } .smhq-mini-btn { background: var(--smhq-accent); color: white; border:none; border-radius:20px; padding:10px 14px; font-size:14px; box-shadow: 0 6px 18px rgba(0,0,0,0.2); cursor:pointer; min-width:140px; } .smhq-panel { background: var(--smhq-bg); color: var(--smhq-fg); width: min(360px, 90vw); max-height: 70vh; overflow:auto; border-radius:12px; box-shadow: 0 14px 40px rgba(0,0,0,0.25); margin-top:8px; } .smhq-header{ display:flex; justify-content:space-between; align-items:center; padding:10px 12px; border-bottom:1px solid rgba(0,0,0,0.06); position:sticky; top:0; background:inherit; z-index:1;} .smhq-title{ font-weight:600; } .smhq-controls button, .smhq-controls select { margin-left:8px; padding:6px 8px; border-radius:8px; border:1px solid rgba(0,0,0,0.08); background:transparent; cursor:pointer; color:inherit; } .smhq-body{ padding:10px 12px; font-size:14px; } .smhq-section{ margin-bottom:10px; } .smhq-subject{ font-weight:600; cursor:pointer; display:flex; justify-content:space-between; align-items:center; padding:8px 4px; border-radius:8px; } .smhq-links{ padding:6px 4px 0 6px; } .smhq-links a{ color:var(--smhq-accent); text-decoration:none; display:block; padding:6px 0; word-break:break-word; } .smhq-small{ font-size:12px; color:rgba(0,0,0,0.6); } .smhq-highlight{ background:var(--smhq-hl-bg); color:var(--smhq-hl-fg); padding:0 2px; border-radius:3px; } .smhq-settings{ padding:10px; border-top:1px dashed rgba(0,0,0,0.06); } .smhq-accordion-item + .smhq-accordion-item{ margin-top:6px; } .smhq-btn { background:var(--smhq-accent); color:#fff; border:none; padding:6px 10px; border-radius:8px; cursor:pointer; } .smhq-toggle{ cursor:pointer; padding:6px; border-radius:6px; } @media (max-width:600px){ .smhq-mini-btn{ font-size:13px; padding:10px 12px; } .smhq-panel{ width: 92vw; right:4vw; } } `; document.head.appendChild(style); } function t(key) { const lang = getLang(); return (STR[lang] && STR[lang][key]) || STR["en"][key] || key; } /* ---------- Highlighting ---------- */ function getTextNodes(root = document.body, limit = 2000) { const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(node) { if (!node.nodeValue || !node.nodeValue.trim()) return NodeFilter.FILTER_REJECT; const parent = node.parentElement; if (!parent) return NodeFilter.FILTER_REJECT; const tag = parent.tagName; if (["SCRIPT", "STYLE", "TEXTAREA", "INPUT"].includes(tag)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }); const nodes = []; while (walker.nextNode() && nodes.length < limit) nodes.push(walker.currentNode); return nodes; } function clearHighlights() { document.querySelectorAll("span.smhq-highlight").forEach(s => s.replaceWith(document.createTextNode(s.textContent))); } function escapeRegExp(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function highlightKeywords(syllabus) { clearHighlights(); const kws = []; for (const cfg of Object.values(syllabus)) for (const k of (cfg.keywords || [])) kws.push(k); if (kws.length === 0) return; kws.sort((a,b)=>b.length - a.length); const nodes = getTextNodes(document.body, 1200); const re = new RegExp("\\b(" + kws.map(k=>escapeRegExp(k)).join("|") + ")\\b","ig"); for (const node of nodes) { const txt = node.nodeValue; if (!re.test(txt)) continue; const frag = document.createDocumentFragment(); let lastIndex = 0; txt.replace(re, (match, ...args) => { const idx = args[args.length - 2]; if (idx > lastIndex) frag.appendChild(document.createTextNode(txt.slice(lastIndex, idx))); const span = document.createElement("span"); span.className = "smhq-highlight"; span.textContent = match; span.style.background = "var(--smhq-hl-bg)"; span.style.color = "var(--smhq-hl-fg)"; span.style.padding = "0 2px"; span.style.borderRadius = "3px"; frag.appendChild(span); lastIndex = idx + match.length; }); if (lastIndex < txt.length) frag.appendChild(document.createTextNode(txt.slice(lastIndex))); node.parentNode.replaceChild(frag, node); } } /* ---------- Scanning ---------- */ function getPageText(limit = 300000) { let t = ""; if (document.body) t = document.body.innerText || ""; if (t.length > limit) t = t.slice(0, limit); return t; } function scanMatches(syllabus, textLC) { const results = []; for (const [sub, cfg] of Object.entries(syllabus)) { for (const kw of cfg.keywords || []) { if (!kw || typeof kw !== "string") continue; const kwLC = kw.toLowerCase(); if (textLC.includes(kwLC)) results.push({ subjectKey: sub, subjectTitle: SUBJECT_TITLES[sub] || { en: sub, hi: sub }, matchedKeyword: kw, links: cfg.links || [] }); } } const seen = new Set(); return results.filter(r => { const k = `${r.subjectKey}::${r.matchedKeyword.toLowerCase()}`; if (seen.has(k)) return false; seen.add(k); return true; }); } function ensureDailySeed() { const today = (new Date()).toISOString().slice(0,10); const storedDate = lsGet(LS.DAILY_DATE, null); if (storedDate !== today) { const seed = Math.floor(Math.random() * 1e9); lsSet(LS.DAILY_DATE, today); lsSet(LS.DAILY_SEED, seed); return seed; } return lsGet(LS.DAILY_SEED, Math.floor(Math.random()*1e9)); } function pickTodaysQuiz(matches, syllabus) { const seed = ensureDailySeed(); let pool = []; if (matches && matches.length > 0) for (const m of matches) pool.push(...(m.links || [])); if (pool.length === 0) for (const cfg of Object.values(syllabus)) pool.push(...(cfg.links || [])); if (pool.length === 0) return null; return pool[seed % pool.length]; } /* ---------- Render widget (panel open state persistence added) ---------- */ function renderWidget(matches, syllabus) { const existing = document.getElementById("smhq-root"); // capture current session state if existing so we can preserve on re-render (sessionStorage is used) // sessionStorage key is authoritative across re-renders during session if (existing) existing.remove(); injectStyles(); const root = document.createElement("div"); root.id = "smhq-root"; root.className = "smhq-wrap"; const exams = getExams() || []; const mini = document.createElement("button"); mini.className = "smhq-mini-btn"; mini.textContent = `${t("title")} • ${exams.join(" + ")} ▸`; const panel = document.createElement("div"); panel.className = "smhq-panel"; // header const header = document.createElement("div"); header.className = "smhq-header"; const title = document.createElement("div"); title.className = "smhq-title"; title.textContent = `${t("title")} • ${exams.join(" + ")}`; const controls = document.createElement("div"); controls.className = "smhq-controls"; // language select const langSel = document.createElement("select"); ["en","hi"].forEach(l => { const op = document.createElement("option"); op.value = l; op.textContent = l === "en" ? "EN" : "हिन्दी"; langSel.appendChild(op); }); langSel.value = getLang(); langSel.onchange = () => { setLang(langSel.value); rebuildUI(); }; // theme select const themeSel = document.createElement("select"); const createOpt = (v, ttxt) => { const o = document.createElement("option"); o.value = v; o.textContent = ttxt; return o; }; themeSel.appendChild(createOpt("auto", t("themeAuto"))); themeSel.appendChild(createOpt("light", t("themeLight"))); themeSel.appendChild(createOpt("dark", t("themeDark"))); themeSel.value = getThemePref(); themeSel.onchange = () => { setThemePref(themeSel.value); applyTheme(); }; // rescan const rescanBtn = document.createElement("button"); rescanBtn.textContent = t("rescan"); rescanBtn.className = "smhq-btn"; rescanBtn.onclick = () => doScanImmediate(); // settings const settingsBtn = document.createElement("button"); settingsBtn.textContent = t("settings"); settingsBtn.onclick = () => toggleSettings(); // minimize const minimizeBtn = document.createElement("button"); minimizeBtn.textContent = t("minimize"); minimizeBtn.onclick = () => { panel.style.display = "none"; mini.style.display = "inline-block"; try { sessionStorage.setItem(PANEL_OPEN_KEY, "false"); } catch (e) {} }; controls.appendChild(langSel); controls.appendChild(themeSel); controls.appendChild(rescanBtn); controls.appendChild(settingsBtn); controls.appendChild(minimizeBtn); header.appendChild(title); header.appendChild(controls); const body = document.createElement("div"); body.className = "smhq-body"; // Today's quiz const today = pickTodaysQuiz(matches, syllabus); if (today) { const tq = document.createElement("div"); tq.className = "smhq-section"; tq.innerHTML = `<div style="font-weight:600">${t("todaysQuiz")}</div>`; const a = document.createElement("a"); a.href = today.url; a.target = "_blank"; a.rel = "noopener"; a.textContent = today.name; a.style.color = "var(--smhq-accent)"; a.style.display = "inline-block"; a.style.marginTop = "6px"; tq.appendChild(a); body.appendChild(tq); } // group matches by subject const bySubject = new Map(); for (const m of matches) { if (!bySubject.has(m.subjectKey)) bySubject.set(m.subjectKey, { title: m.subjectTitle, keywords: new Set(), links: m.links || [] }); bySubject.get(m.subjectKey).keywords.add(m.matchedKeyword); } if (bySubject.size === 0) { const hint = document.createElement("div"); hint.className = "smhq-small"; hint.textContent = "No syllabus keywords detected on this page."; body.appendChild(hint); } const accordionRoot = document.createElement("div"); accordionRoot.id = "smhq-accordion-root"; for (const [subKey, info] of bySubject.entries()) { const item = document.createElement("div"); item.className = "smhq-accordion-item smhq-section"; const subjBar = document.createElement("div"); subjBar.className = "smhq-subject"; const subjTitle = document.createElement("div"); const labelTitle = (SUBJECT_TITLES[subKey] && SUBJECT_TITLES[subKey][getLang()]) || subKey; subjTitle.textContent = `${labelTitle} → ${Array.from(info.keywords).join(", ")}`; const chevron = document.createElement("div"); chevron.textContent = "▸"; subjBar.appendChild(subjTitle); subjBar.appendChild(chevron); const linksWrap = document.createElement("div"); linksWrap.className = "smhq-links"; linksWrap.style.display = "none"; for (const link of info.links) { const a = document.createElement("a"); a.href = link.url; a.target = "_blank"; a.rel = "noopener"; a.textContent = link.name; const actionWrap = document.createElement("div"); actionWrap.style.display = "flex"; actionWrap.style.justifyContent = "space-between"; actionWrap.style.alignItems = "center"; const left = document.createElement("div"); left.appendChild(a); const right = document.createElement("div"); right.style.display = "flex"; right.style.gap = "8px"; const copyBtn = document.createElement("button"); copyBtn.textContent = "⧉"; copyBtn.title = t("copyLink"); copyBtn.onclick = (e) => { e.preventDefault(); navigator.clipboard?.writeText(link.url); copyBtn.textContent = "✓"; setTimeout(()=>copyBtn.textContent="⧉", 1200); }; const saveBtn = document.createElement("button"); saveBtn.textContent = "☆"; saveBtn.title = t("bookmark"); saveBtn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); const saved = getSavedLinks(); saved.push({ name: link.name, url: link.url, subject: subKey, date: new Date().toISOString() }); setSavedLinks(saved); saveBtn.textContent="★"; setTimeout(()=>saveBtn.textContent="☆",800); }; right.appendChild(copyBtn); right.appendChild(saveBtn); actionWrap.appendChild(left); actionWrap.appendChild(right); linksWrap.appendChild(actionWrap); } subjBar.onclick = () => { const open = linksWrap.style.display === "block"; linksWrap.style.display = open ? "none" : "block"; chevron.textContent = open ? "▸" : "▾"; }; item.appendChild(subjBar); item.appendChild(linksWrap); accordionRoot.appendChild(item); } body.appendChild(accordionRoot); // settings area const settingsArea = document.createElement("div"); settingsArea.className = "smhq-settings"; settingsArea.style.display = "none"; const hlLabel = document.createElement("label"); hlLabel.style.display = "block"; hlLabel.style.marginBottom = "8px"; const hlCb = document.createElement("input"); hlCb.type = "checkbox"; hlCb.checked = getHighlightPref(); hlCb.onchange = () => { setHighlightPref(hlCb.checked); if (hlCb.checked) highlightKeywords(syllabus); else clearHighlights(); }; hlLabel.appendChild(hlCb); hlLabel.appendChild(document.createTextNode(" " + t("highlightToggle"))); settingsArea.appendChild(hlLabel); const ckTitle = document.createElement("div"); ckTitle.textContent = t("customKeywords"); ckTitle.style.fontWeight = "600"; ckTitle.style.marginTop = "8px"; settingsArea.appendChild(ckTitle); const ckContainer = document.createElement("div"); ckContainer.style.maxHeight = "160px"; ckContainer.style.overflow = "auto"; ckContainer.style.marginBottom = "8px"; const customKw = getCustomKeywords(); for (const sub of Object.keys(buildSyllabus())) { const row = document.createElement("div"); row.style.marginBottom = "6px"; const lab = document.createElement("div"); lab.style.fontSize = "13px"; lab.style.fontWeight = "600"; lab.textContent = (SUBJECT_TITLES[sub] && SUBJECT_TITLES[sub][getLang()]) || sub; const input = document.createElement("input"); input.type = "text"; input.style.width = "100%"; input.style.marginTop = "4px"; input.placeholder = "comma,separated,keywords"; input.value = (customKw[sub] || []).join(","); const saveBtn = document.createElement("button"); saveBtn.textContent = t("save"); saveBtn.style.marginTop = "6px"; saveBtn.onclick = () => { const val = input.value.split(",").map(s=>s.trim()).filter(s=>s); const curr = getCustomKeywords(); if (val.length>0) curr[sub]=val; else delete curr[sub]; setCustomKeywords(curr); alert("Saved"); }; row.appendChild(lab); row.appendChild(input); row.appendChild(saveBtn); ckContainer.appendChild(row); } settingsArea.appendChild(ckContainer); const ieTitle = document.createElement("div"); ieTitle.textContent = t("importExport"); ieTitle.style.fontWeight = "600"; settingsArea.appendChild(ieTitle); const ieArea = document.createElement("div"); const txtArea = document.createElement("textarea"); txtArea.placeholder = t("importPlaceholder"); txtArea.style.width = "100%"; txtArea.style.height = "80px"; const expBtn = document.createElement("button"); expBtn.textContent = t("export"); expBtn.onclick = () => { const exportObj = { exams: getExams(), lang: getLang(), muted: lsGet(LS.MUTED, {}), customKeywords: getCustomKeywords(), theme: getThemePref(), highlight: getHighlightPref() }; txtArea.value = JSON.stringify(exportObj, null, 2); }; const impBtn = document.createElement("button"); impBtn.textContent = t("importBtn"); impBtn.onclick = () => { try { const obj = JSON.parse(txtArea.value); if (obj.exams) setExams(obj.exams); if (obj.lang) setLang(obj.lang); if (obj.muted) lsSet(LS.MUTED, obj.muted); if (obj.customKeywords) setCustomKeywords(obj.customKeywords); if (obj.theme) setThemePref(obj.theme); if (typeof obj.highlight !== "undefined") setHighlightPref(!!obj.highlight); alert("Imported"); } catch (e) { alert("Invalid JSON"); } }; ieArea.appendChild(txtArea); ieArea.appendChild(expBtn); ieArea.appendChild(impBtn); settingsArea.appendChild(ieArea); const savedTitle = document.createElement("div"); savedTitle.textContent = t("savedLinks"); savedTitle.style.fontWeight = "600"; savedTitle.style.marginTop = "8px"; settingsArea.appendChild(savedTitle); const savedWrap = document.createElement("div"); function renderSaved() { savedWrap.innerHTML = ""; const saved = getSavedLinks(); if (!saved || saved.length === 0) savedWrap.textContent = "—"; else { for (const s of saved.slice().reverse()) { const r = document.createElement("div"); r.style.display = "flex"; r.style.justifyContent = "space-between"; r.style.marginBottom = "6px"; const l = document.createElement("a"); l.href = s.url; l.target = "_blank"; l.rel = "noopener"; l.textContent = `${s.name} (${s.subject})`; const rm = document.createElement("button"); rm.textContent = "✕"; rm.onclick = () => { const arr = getSavedLinks().filter(x=>x.url!==s.url); setSavedLinks(arr); renderSaved(); }; r.appendChild(l); r.appendChild(rm); savedWrap.appendChild(r); } } } renderSaved(); settingsArea.appendChild(savedWrap); // assemble panel.appendChild(header); panel.appendChild(body); panel.appendChild(settingsArea); // mute label const muteLabel = document.createElement("label"); muteLabel.style.padding = "8px 12px"; const muteCb = document.createElement("input"); muteCb.type = "checkbox"; muteCb.checked = isMutedHere(); muteCb.onchange = () => { setMutedHere(!!muteCb.checked); if (muteCb.checked) { root.remove(); } }; muteLabel.appendChild(muteCb); muteLabel.appendChild(document.createTextNode(" " + t("muteSite"))); panel.appendChild(muteLabel); root.appendChild(mini); root.appendChild(panel); document.body.appendChild(root); // Apply theme function applyTheme() { const pref = getThemePref(); if (pref === "auto") { document.documentElement.style.removeProperty('--smhq-bg'); document.documentElement.style.removeProperty('--smhq-fg'); document.documentElement.style.removeProperty('--smhq-accent'); } else if (pref === "light") { document.documentElement.style.setProperty('--smhq-bg', '#ffffff'); document.documentElement.style.setProperty('--smhq-fg', '#111'); document.documentElement.style.setProperty('--smhq-accent', '#0b5ed7'); } else { document.documentElement.style.setProperty('--smhq-bg', '#0f1720'); document.documentElement.style.setProperty('--smhq-fg', '#e6eef8'); document.documentElement.style.setProperty('--smhq-accent', '#558bff'); } } applyTheme(); // settings toggle function toggleSettings() { settingsArea.style.display = settingsArea.style.display === "none" ? "block" : "none"; } // rebuild UI helper function rebuildUI() { const matchesNow = lastMatchesCache || []; root.remove(); renderWidget(matchesNow, syllabus); } // session persistence for panel state const panelOpen = (function(){ try { return sessionStorage.getItem(PANEL_OPEN_KEY) === "true"; } catch { return false; } })(); // set initial visibility based on sessionStorage if (panelOpen) { panel.style.display = "block"; mini.style.display = "none"; if (getHighlightPref()) highlightKeywords(syllabus); } else { panel.style.display = "none"; mini.style.display = "inline-block"; } // expand action: one-way expand until user minimizes (persist) mini.onclick = () => { panel.style.display = "block"; mini.style.display = "none"; try { sessionStorage.setItem(PANEL_OPEN_KEY, "true"); } catch (e) {} if (getHighlightPref()) highlightKeywords(syllabus); }; // expose some utilities for buttons window.__smhq_rebuildUI = rebuildUI; window.__smhq_toggleSettings = toggleSettings; window.__smhq_applyTheme = applyTheme; window.__smhq_renderSaved = renderSaved; } /* ---------- Scanning loop & observer ---------- */ let lastHash = null; let lastMatchesCache = []; let debounceTimer = null; function textHash(s) { let h = 0; for (let i = 0; i < s.length; i++) h = ((h << 5) - h) + s.charCodeAt(i), h |= 0; return h; } function doScanImmediate() { const syllabus = buildSyllabus(); const txt = getPageText(); const lc = txt.toLowerCase(); const h = textHash(lc.slice(0, 120000)); if (h === lastHash) return; lastHash = h; const matches = scanMatches(syllabus, lc); lastMatchesCache = matches; if (isMutedHere()) return; renderWidget(matches, syllabus); if (getHighlightPref()) highlightKeywords(syllabus); } function scheduleScan() { if (debounceTimer) clearTimeout(debounceTimer); debounceTimer = setTimeout(doScanImmediate, 600); } function startObserver() { const mo = new MutationObserver(scheduleScan); mo.observe(document.documentElement || document.body, { childList: true, subtree: true, characterData: false }); scheduleScan(); } /* ---------- Exam modal ---------- */ function askExamsModal(onDone) { const lang = getLang(); const S = STR[lang]; const overlay = document.createElement("div"); overlay.style.position = "fixed"; overlay.style.inset = "0"; overlay.style.background = "rgba(0,0,0,0.5)"; overlay.style.zIndex = 2147483646; overlay.style.display = "flex"; overlay.style.alignItems = "center"; overlay.style.justifyContent = "center"; const box = document.createElement("div"); box.style.background = "var(--smhq-bg)"; box.style.color = "var(--smhq-fg)"; box.style.padding = "18px"; box.style.borderRadius = "12px"; box.style.width = "320px"; box.style.boxShadow = "0 12px 40px rgba(0,0,0,0.25)"; box.style.fontFamily = "system-ui, sans-serif"; box.style.textAlign = "center"; const title = document.createElement("h3"); title.textContent = S.selectExamsTitle; title.style.margin = "0 0 12px"; box.appendChild(title); const form = document.createElement("form"); form.style.textAlign = "left"; ["UPSC","SSC CGL"].forEach(ex => { const label = document.createElement("label"); label.style.display = "block"; label.style.margin = "8px 0"; const input = document.createElement("input"); input.type = "checkbox"; input.value = ex; input.style.marginRight = "8px"; label.appendChild(input); label.appendChild(document.createTextNode(ex)); form.appendChild(label); }); const submit = document.createElement("button"); submit.type = "submit"; submit.textContent = S.save; submit.style.marginTop = "12px"; submit.style.padding = "8px 14px"; submit.style.borderRadius = "8px"; submit.style.border = "none"; submit.style.cursor = "pointer"; submit.style.background = "#0b5ed7"; submit.style.color = "#fff"; form.appendChild(submit); form.onsubmit = (e) => { e.preventDefault(); const chosen = Array.from(form.querySelectorAll("input:checked")).map(i => i.value); if (chosen.length === 0) { alert(S.atLeastOne); return; } setExams(chosen); overlay.remove(); onDone(chosen); }; box.appendChild(form); overlay.appendChild(box); document.body.appendChild(overlay); } /* ---------- Bootstrap & GM menu ---------- */ function boot() { injectStyles(); if (isMutedHere()) return; const exams = getExams(); if (!exams) { askExamsModal((chosen)=>{ setExams(chosen); boot(); }); return; } if (!lsGet(LS.LANG)) setLang("en"); if (!lsGet(LS.THEME)) setThemePref("auto"); if (lsGet(LS.HIGHLIGHT) === null) setHighlightPref(true); ensureDailySeed(); startObserver(); } if (typeof GM_registerMenuCommand === "function") { GM_registerMenuCommand("Change exam(s) / Reset preferences", () => { if (confirm("Reset Study Boost preferences? This clears exam selection, language, custom keywords and muted sites.")) { localStorage.removeItem(LS.EXAMS); localStorage.removeItem(LS.LANG); localStorage.removeItem(LS.MUTED); localStorage.removeItem(LS.CUSTOM_KW); localStorage.removeItem(LS.THEME); localStorage.removeItem(LS.HIGHLIGHT); localStorage.removeItem(LS.SAVED); localStorage.removeItem(LS.DAILY_DATE); localStorage.removeItem(LS.DAILY_SEED); try { sessionStorage.removeItem(PANEL_OPEN_KEY); } catch {} location.reload(); } }); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot, { once: true }); else boot(); // expose scan button in console if needed window.__smhq_scanNow = doScanImmediate; })();