Syllabus MCQ Helper — Advanced (UPSC / SSC CGL) — Panel stays open fix

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;

})();