(() => {
  "use strict";

  // ---- Engine presets (mirror of the app's SearchEngines) ----
  const ENGINES = {
    niguro: { name: "Niguro", search: "https://www.niguro.com/search/result?q=", suggest: "https://suggestqueries.google.com/complete/search?client=chrome&q=" },
    google: { name: "Google", search: "https://www.google.com/search?q=", suggest: "https://suggestqueries.google.com/complete/search?client=chrome&q=" },
    bing: { name: "Bing", search: "https://www.bing.com/search?q=", suggest: "https://api.bing.com/osjson.aspx?query=" },
    startpage: { name: "Startpage", search: "https://www.startpage.com/sp/search?query=", suggest: "" },
    duckduckgo: { name: "DuckDuckGo", search: "https://duckduckgo.com/?q=", suggest: "" },
  };

  // ---- State ----
  let engine = localStorage.getItem("pako-engine") || "niguro";
  let CUSTOMS = []; // [{Id, Name, Template, SuggestUrl}] from app settings
  let suggestions = [];
  let TRENDING = []; // Niguro trending keywords (from the app bridge)
  let NEWS = [];     // {Feed, Source, Title, Link, Date, Lang, Excerpt, Image} from news.get
  let newsLang = "all"; // all | ne | en
  let appTheme = null; // "light" | "dark" | "auto" — the app's chosen mode

  // ---- Non-emoji stroke icons ----
  const ICON_SEARCH = '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 21l-4.35-4.35M3 11a8 8 0 1 0 16 0 8 8 0 1 0-16 0z"/></svg>';
  const ICON_TREND = '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 17l6-6 4 4 8-8M14 7h7v7"/></svg>';
  const ICON_X = '<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>';

  // ---- Engine helpers (presets + user-added customs) ----
  function engInfo(id) {
    const c = CUSTOMS.find((x) => x.Id === id);
    if (c && c.Template) return { name: c.Name || "Custom", search: c.Template, suggest: c.SuggestUrl || "" };
    return ENGINES[id] || ENGINES.niguro;
  }
  function isCustom(id) { return CUSTOMS.some((c) => c.Id === id); }
  function searchUrlFor(id, query) {
    const eng = engInfo(id);
    const enc = encodeURIComponent(query);
    return isCustom(id) ? eng.search.replace("{q}", enc) : eng.search + enc;
  }

  // ---- Bridge to the host app (WebView2) ----
  const bridge = window.chrome && window.chrome.webview ? window.chrome.webview : null;
  function ask(type) { if (bridge) bridge.postMessage({ type }); }
  // IMPORTANT: WebView2 delivers host→page messages on `window.chrome.webview`'s
  // message event — NOT the window's. Listen on both so this works in and out of WebView2.
  function onHostMessage(e) {
    try {
      const msg = typeof e.data === "string" ? JSON.parse(e.data) : e.data;
      if (!msg || !msg.type) return;
      if (msg.type === "settings.changed" && msg.data) {
        applyAppSettings(msg.data);
      } else if (msg.type === "settings.data" && msg.data) {
        applyAppSettings(msg.data);
      } else if (msg.type === "settings.reset") {
        // Factory reset from Settings — the fresh snapshot already carries the defaults
        // (null shortcuts, empty wallpaper, System theme, Niguro engine), so apply it
        // in place. Search history is browsing data and is deliberately kept.
        applyAppSettings(msg.data);
      } else if (msg.type === "theme") {
        // Direct answer to browser.theme — covers System mode with the computed dark flag.
        if (msg.accent) {
          document.documentElement.style.setProperty("--accent", msg.accent);
          ACCENT = msg.accent;
        }
        if (msg.dark === true || msg.dark === false) {
          appTheme = msg.dark ? "dark" : "light";
          setTheme(msg.dark ? "dark" : "light");
          persistTheme();
        }
      } else if (msg.type === "trending.data" && Array.isArray(msg.items)) {
        TRENDING = msg.items;
        // Only surface trending if the user actually has the input active — never on page load.
        if (document.activeElement === q && !q.value.trim()) showHistory();
      } else if (msg.type === "news.data" && msg.data) {
        // Bhitta-style news strip — same feed as niguro://news, from the app.
        NEWS = (msg.data.Items || []).slice(0, 30);
        renderNews();
      } else if (msg.type === "update.available") {
        showUpdToast(msg);
      }
    } catch { }
  }

  // ---- Update available: small floating pill at the bottom of the page ----
  let _updWired = false;
  function showUpdToast(msg) {
    const el = document.getElementById("updToast");
    if (!el) return;
    if (!msg || !msg.data) { el.classList.add("hidden"); return; }
    document.getElementById("updVer").textContent = msg.data.version;
    el.classList.remove("hidden");
    if (_updWired) return;
    _updWired = true;
    document.getElementById("updApplyBtn").addEventListener("click", () => {
      if (bridge) bridge.postMessage({ type: "update.apply" });
    });
    document.getElementById("updWhatsNew").addEventListener("click", () => {
      if (bridge) bridge.postMessage({ type: "nav.whatsnew" });
    });
    document.getElementById("updClose").addEventListener("click", () => {
      el.classList.add("hidden");
    });
  }
  if (bridge) bridge.addEventListener("message", onHostMessage);
  window.addEventListener("message", onHostMessage);
  function applyAppSettings(s) {
    if (s && s.Accent) {
      document.documentElement.style.setProperty("--accent", s.Accent);
      ACCENT = s.Accent;
      applyAccentSurfaces();
    }
    if (s && s.Theme === "Light") { setTheme("light"); persistTheme(); }
    else if (s && s.Theme === "Dark") { setTheme("dark"); persistTheme(); }
    else if (s && s.Theme === "System") { appTheme = "auto"; localStorage.setItem("pako-theme", "auto"); setTheme("auto"); }
    if (s && Array.isArray(s.CustomEngines)) CUSTOMS = s.CustomEngines;
    if (s && s.SearchEngineId && (ENGINES[s.SearchEngineId] || isCustom(s.SearchEngineId))) {
      engine = s.SearchEngineId;
      localStorage.setItem("pako-engine", engine);
      updatePlaceholder();
    }
    // Apply whatever the app has — including an empty string (wallpaper removed in
    // Settings → clears it here too, so both stay in sync).
    if (s && typeof s.NewTabWallpaper === "string") {
      applyWallpaper(s.NewTabWallpaper, true);
    }
    // Widget toggles mirror the app settings (shortcuts / date / quick links / events / news).
    if (s && typeof s.ShowShortcuts === "boolean") document.getElementById("quickRow").classList.toggle("hidden", !s.ShowShortcuts);
    if (s && typeof s.NewTabShowDate === "boolean") {
      const d = document.getElementById("dateBtn");
      if (d) { d.classList.toggle("hidden", !s.NewTabShowDate); d.nextElementSibling?.classList.toggle("hidden", !s.NewTabShowDate); }
    }
    if (s && typeof s.NewTabShowQuickLinks === "boolean") document.getElementById("quicklinks").classList.toggle("hidden", !s.NewTabShowQuickLinks);
    if (s && typeof s.NewTabShowEvents === "boolean") document.getElementById("festiRow").classList.toggle("hidden", !s.NewTabShowEvents);
    if (s && typeof s.NewTabShowNews === "boolean") document.getElementById("newsStrip").classList.toggle("hidden", !s.NewTabShowNews);
    // Keep the Customize → Widgets toggles in step with the app settings.
    syncWidgetToggles(s);
    // The header pin button adds shortcuts in the app (chrome) — mirror them into the
    // page's own store so they appear here too. The page already mirrors back on its
    // own edits, so this is a safe union (the app list is authoritative when pinned).
    if (s && Array.isArray(s.NewTabShortcuts) && s.NewTabShortcuts.length) {
      const mapped = s.NewTabShortcuts.map((x) => ({
        label: x.Label || x.label || x.Url || x.url || "",
        url: x.Url || x.url || "",
        icon: x.icon || "",
      })).filter((x) => x.url);
      localStorage.setItem("pako-shortcuts-v2", JSON.stringify(mapped));
      renderShortcuts();
    } else if (s && s.NewTabShortcuts === null) {
      // Reset (or never customized): the app has no shortcuts — drop the page's own
      // store so the built-in defaults take over. Covers new tabs opened AFTER a
      // settings reset, not just the one that was open during it.
      try { localStorage.removeItem("pako-shortcuts-v2"); } catch (e) {}
      renderShortcuts();
    }
  }
  ask("settings.get");
  ask("browser.theme");
  ask("trending.get");
  ask("update.status");

  // ---- Accent-hued dark surfaces (like niguro.com's dark mode) ----
  let ACCENT = "#04B430";
  function hexToRgb(hex) {
    let h = String(hex).replace("#", "");
    if (h.length === 3) h = h.split("").map((c) => c + c).join("");
    const n = parseInt(h, 16);
    if (isNaN(n)) return { r: 4, g: 180, b: 48 };
    return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
  }
  function tinted(c, k) { return `rgb(${Math.round(c.r * k)},${Math.round(c.g * k)},${Math.round(c.b * k)})`; }
  function applyAccentSurfaces() {
    const c = hexToRgb(ACCENT);
    const dark = document.documentElement.dataset.theme === "dark";
    const d = document.documentElement;
    if (dark) {
      // Solid, darker accent surfaces in dark mode (dropdowns darker than the rest).
      d.style.setProperty("--bg", tinted(c, 0.045));
      d.style.setProperty("--card", tinted(c, 0.10));
      d.style.setProperty("--card-solid", tinted(c, 0.06));
      d.style.setProperty("--border", `rgba(${c.r},${c.g},${c.b},0.30)`);
    } else {
      d.style.removeProperty("--bg");
      d.style.removeProperty("--card");
      d.style.removeProperty("--card-solid");
      d.style.removeProperty("--border");
    }
  }

  // ---- Theme ----
  function savedTheme() { return localStorage.getItem("pako-theme") || "auto"; }
  function persistTheme() { localStorage.setItem("pako-theme", appTheme || "auto"); }
  setTheme(savedTheme());
  document.getElementById("themeBtn").addEventListener("click", () => {
    const next = document.documentElement.dataset.theme === "dark" ? "light" : "dark";
    appTheme = next;
    setTheme(next);
    persistTheme();
    // Sync to the whole browser (chrome + header + url bar), not just this page.
    if (bridge) bridge.postMessage({ type: "settings.set", data: { Theme: next === "dark" ? "Dark" : "Light" } });
  });
  // Follow the OS when in System/auto mode.
  window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (m) => {
    const mode = appTheme || savedTheme();
    if (mode === "auto") setTheme(m.matches ? "dark" : "light");
  });
  function setTheme(t) {
    const dark = t === "dark" || (t === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches);
    document.documentElement.dataset.theme = dark ? "dark" : "light";
    applyAccentSurfaces();
    updateLogo();
  }
  // Brand logo: day logo in day mode, dark logo in dark AND wallpaper mode.
  function updateLogo() {
    const dark = document.documentElement.dataset.theme === "dark";
    const wallpaper = document.body.classList.contains("art");
    const logo = document.getElementById("logo");
    if (logo) logo.src = (dark || wallpaper) ? "Pakodarklogo.svg" : "Pakodaylogo.svg";
  }

  // Placeholder reflects the active search engine (changed in Settings now — no picker).
  function updatePlaceholder() {
    const q = document.getElementById("q");
    q.placeholder = `Search ${engInfo(engine).name} or type a URL…`;
  }

  // ---- Date chip (AD + BS) ----
  function fmtAdDate(ad) {
    const d = new Date(ad.y, ad.m - 1, ad.d);
    return d.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
  }
  function fillBsSelects(bs) {
    const N = window.NepaliDate;
    const ySel = document.getElementById("calBsY");
    const mSel = document.getElementById("calBsM");
    const dSel = document.getElementById("calBsD");
    if (!ySel) return;
    // Build year + month options once.
    if (ySel.options.length === 0) {
      for (let y = N.range.bsFirst; y <= N.range.bsLast; y++) {
        const o = document.createElement("option");
        o.value = y; o.textContent = N.npNum(y) + " (" + y + ")";
        ySel.appendChild(o);
      }
      N.monthNp.forEach((np, i) => {
        const o = document.createElement("option");
        o.value = i + 1; o.textContent = np + " · " + N.monthEn[i];
        mSel.appendChild(o);
      });
    }
    ySel.value = bs.y; mSel.value = bs.m;
    rebuildBsDays(bs.y, bs.m, bs.d);
  }
  function rebuildBsDays(by, bm, keepDay) {
    const N = window.NepaliDate;
    const dSel = document.getElementById("calBsD");
    if (!dSel) return;
    const last = N.daysIn(by, bm - 1);
    dSel.innerHTML = "";
    for (let d = 1; d <= last; d++) {
      const o = document.createElement("option");
      o.value = d; o.textContent = N.npNum(d);
      dSel.appendChild(o);
    }
    dSel.value = keepDay;
    if (!dSel.value && last > 0) dSel.value = 1;
  }
  function renderBsToAd() {
    const N = window.NepaliDate;
    const by = +document.getElementById("calBsY").value;
    const bm = +document.getElementById("calBsM").value;
    const bd = +document.getElementById("calBsD").value;
    if (!by || !bm || !bd) return;
    const ad = N.bsToAd(by, bm, bd);
    document.getElementById("calBsResult").innerHTML =
      "<div class=\"cal-result-big\">" + esc(fmtAdDate(ad)) + "</div>" +
      "<div class=\"cal-result-sub\">" + esc(N.npNum(bd) + " " + N.monthNp[bm - 1] + " " + N.npNum(by) + " BS") + "</div>";
  }
  function renderAdToBs() {
    const N = window.NepaliDate;
    const v = document.getElementById("calAd").value;
    if (!v) return;
    const p = v.split("-");
    const bs = N.adToBs(+p[0], +p[1], +p[2]);
    const ad = { y: +p[0], m: +p[1], d: +p[2] };
    const dw = new Date(p[0], p[1] - 1, p[2]).getDay(); // 0 Sun
    document.getElementById("calAdResult").innerHTML =
      "<div class=\"cal-result-big\">" + esc(N.monthNp[bs.m - 1] + " " + N.npNum(bs.d) + ", " + N.npNum(bs.y)) + "</div>" +
      "<div class=\"cal-result-sub\">" + esc(N.monthEn[bs.m - 1] + " " + bs.d + ", " + bs.y + " BS · " + (N.weekNp[dw] || "")) + "</div>" +
      "<div class=\"cal-result-sub\">" + esc(fmtAdDate(ad)) + "</div>";
    fillBsSelects(bs);
    renderBsToAd();
  }
  function syncFromBs() {
    const N = window.NepaliDate;
    const by = +document.getElementById("calBsY").value;
    const bm = +document.getElementById("calBsM").value;
    const bd = +document.getElementById("calBsD").value;
    const ad = N.bsToAd(by, bm, bd);
    const iso = ad.y + "-" + String(ad.m).padStart(2, "0") + "-" + String(ad.d).padStart(2, "0");
    document.getElementById("calAd").value = iso;
    renderAdToBs();
    renderBsToAd();
  }
  function renderDateChip() {
    const dateEl = document.getElementById("dateStr");
    const bsEl = document.getElementById("dateBs");
    if (!dateEl) return;
    const N = window.NepaliDate;
    const now = new Date();
    dateEl.textContent = now.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
    if (bsEl && N) {
      const bs = N.todayBs();
      bsEl.textContent = N.monthNp[bs.m - 1] + " " + N.npNum(bs.d) + ", " + N.npNum(bs.y);
    }
  }
  function openCal() {
    const N = window.NepaliDate;
    const now = new Date();
    const iso = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0") + "-" + String(now.getDate()).padStart(2, "0");
    document.getElementById("calAd").value = iso;
    const bs = N.todayBs();
    fillBsSelects(bs);
    renderAdToBs();
    renderBsToAd();
    document.getElementById("calModal").classList.remove("hidden");
  }
  const dateBtn = document.getElementById("dateBtn");
  if (dateBtn) dateBtn.addEventListener("click", openCal);
  const calModal = document.getElementById("calModal");
  if (calModal) {
    document.getElementById("calClose").addEventListener("click", () => calModal.classList.add("hidden"));
    calModal.addEventListener("click", (e) => { if (e.target === calModal) calModal.classList.add("hidden"); });
    document.getElementById("calAd").addEventListener("change", renderAdToBs);
    document.getElementById("calBsY").addEventListener("change", () => {
      const by = +document.getElementById("calBsY").value;
      const bm = +document.getElementById("calBsM").value;
      rebuildBsDays(by, bm, 1);
      syncFromBs();
    });
    document.getElementById("calBsM").addEventListener("change", () => {
      const by = +document.getElementById("calBsY").value;
      const bm = +document.getElementById("calBsM").value;
      rebuildBsDays(by, bm, 1);
      syncFromBs();
    });
    document.getElementById("calBsD").addEventListener("change", syncFromBs);
    document.getElementById("calToday").addEventListener("click", () => {
      const N = window.NepaliDate;
      const now = new Date();
      const iso = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0") + "-" + String(now.getDate()).padStart(2, "0");
      document.getElementById("calAd").value = iso;
      renderAdToBs();
      fillBsSelects(N.todayBs());
      renderBsToAd();
    });
  }
  renderDateChip();
  setInterval(renderDateChip, 60000); // roll over at midnight


  // ---- Festival countdown chips (Dashain / Tihar / Teej — like Bhitta) ----
  // AD anchors refreshed each Nepali year; only upcoming ones render, max 3.
  const FESTIVALS = [
    { name: "Haritalika Teej", date: "2026-09-14" },
    { name: "Dashain Tika", date: "2026-10-21" },
    { name: "Tihar · Laxmi Puja", date: "2026-11-08" },
  ];
  function renderFestivalChips() {
    const row = document.getElementById("festiRow");
    if (!row) return;
    const now = new Date();
    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const upcoming = FESTIVALS
      .map((f) => {
        const d = new Date(f.date + "T00:00:00");
        const days = Math.round((d - today) / 86400000);
        return { name: f.name, days };
      })
      .filter((f) => f.days >= 0)
      .sort((a, b) => a.days - b.days)
      .slice(0, 3);
    row.innerHTML = upcoming.map((f) =>
      "<span class='festi-chip'><span class='d'>" +
      (f.days === 0 ? "Today" : f.days === 1 ? "Tomorrow" : "in " + f.days + " days") +
      "</span><span>" + f.name + "</span></span>"
    ).join("");
  }
  renderFestivalChips();
  setInterval(renderFestivalChips, 60000);


  // ---- Shortcuts ----
  // Defaults: 4 popular sites with REAL favicons via Google's favicon service — they
  // load instantly (no page load needed). Users can have up to 8 shortcuts total.
  const MAX_SHORTCUTS = 8;
  const DEFAULT_SHORTCUTS = [
    { label: "Niguro", icon: "https://www.google.com/s2/favicons?domain=niguro.com&sz=64", url: "https://www.niguro.com/" },
    { label: "ChatGPT", icon: "https://www.google.com/s2/favicons?domain=chatgpt.com&sz=64", url: "https://chatgpt.com/" },
    { label: "Facebook", icon: "https://www.google.com/s2/favicons?domain=facebook.com&sz=64", url: "https://www.facebook.com/" },
    { label: "X", icon: "https://www.google.com/s2/favicons?domain=x.com&sz=64", url: "https://x.com/" },
  ];
  const QUICKLINKS = [
    { label: "Niguro", url: "https://www.niguro.com/" },
    { label: "YouTube", url: "https://www.youtube.com/" },
    { label: "Gmail", url: "https://mail.google.com/" },
    { label: "Maps", url: "https://maps.google.com/" },
  ];
  const shortcutsEl = document.getElementById("shortcuts");
  const q = document.getElementById("q");
  const suggestEl = document.getElementById("suggest");
  const historyEl = document.getElementById("history");
  const clearQBtn = document.getElementById("clearQ");

  // Bhitta-style icon: user-added shortcuts show the site's REAL favicon (Google's
  // favicon service) instead of a generic emoji; a typed emoji still overrides.
  function faviconFor(url) {
    try {
      const u = new URL(/^https?:\/\//i.test(url) ? url : "https://" + url);
      return "https://www.google.com/s2/favicons?domain=" + u.hostname + "&sz=64";
    } catch (e) { return ""; }
  }
  function shortcutIcon(s) {
    // Explicit favicon URL wins; otherwise pull the site's REAL favicon from Google's
    // favicon service by its URL — only a typed emoji still overrides. The 🔗 fallback
    // is last-resort (e.g. an unparseable URL).
    let src = "";
    const custom = s.icon && String(s.icon).trim();
    if (s.icon && /^https?:\/\//i.test(s.icon)) src = s.icon;
    else if (!custom) src = faviconFor(s.url);
    const ico = document.createElement("span");
    ico.className = "icon";
    if (src) {
      const img = document.createElement("img");
      img.className = "sc-fav";
      img.src = src;
      img.alt = "";
      // No lazy loading — favicons should appear instantly, not on scroll.
      img.addEventListener("error", () => { img.remove(); ico.textContent = "🔗"; });
      ico.appendChild(img);
    } else {
      ico.textContent = custom || "🔗";
    }
    return ico;
  }
  function renderShortcuts() {
    const list = getShortcuts();
    shortcutsEl.innerHTML = "";
    list.forEach((s, i) => {
      const a = document.createElement("a");
      a.className = "shortcut";
      a.href = s.url;
      const ico = shortcutIcon(s);
      const del = document.createElement("button");
      del.className = "sc-del";
      del.title = "Remove shortcut";
      del.setAttribute("aria-label", "Remove shortcut");
      del.textContent = "✕";
      del.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        const l = getShortcuts();
        l.splice(i, 1);
        saveShortcuts(l);
        renderShortcuts();
        mirrorShortcuts();
      });
      ico.appendChild(del);
      const label = document.createElement("span");
      label.className = "label";
      label.textContent = s.label;
      a.appendChild(ico);
      a.appendChild(label);
      a.addEventListener("click", (e) => {
        if (e.target.closest(".sc-del")) return;
        e.preventDefault();
        window.location.href = s.url;
      });
      shortcutsEl.appendChild(a);
    });
    // Only show the + Add tile while there's room (max 8 shortcuts).
    if (list.length < MAX_SHORTCUTS) {
      const add = document.createElement("div");
      add.className = "shortcut add-shortcut";
      add.innerHTML = `<span class="icon">＋</span><span class="label">Add</span>`;
      add.addEventListener("click", openShortcutModal);
      shortcutsEl.appendChild(add);
    }
  }
  function getShortcuts() {
    // v2 key: new 4-site defaults; v1 users get the fresh set (browser is pre-release).
    try { return JSON.parse(localStorage.getItem("pako-shortcuts-v2")) || DEFAULT_SHORTCUTS; }
    catch { return DEFAULT_SHORTCUTS; }
  }
  function saveShortcuts(list) { localStorage.setItem("pako-shortcuts-v2", JSON.stringify(list)); }

  // Add-shortcut dialog (custom in-app modal, no browser prompt).
  const scModal = document.getElementById("scModal");
  function openShortcutModal() {
    document.getElementById("scLabel").value = "";
    document.getElementById("scUrl").value = "";
    document.getElementById("scIcon").value = "";
    scModal.classList.remove("hidden");
    setTimeout(() => document.getElementById("scLabel").focus(), 60);
  }
  function closeShortcutModal() { scModal.classList.add("hidden"); }
  function saveShortcutModal() {
    const label = document.getElementById("scLabel").value.trim();
    const url = document.getElementById("scUrl").value.trim();
    if (!label || !url) return;
    // No emoji typed → use the site's real favicon (like Bhitta on niguro.com).
    const icon = document.getElementById("scIcon").value.trim() || faviconFor(url);
    const l = getShortcuts();
    if (l.length >= MAX_SHORTCUTS) return; // hard cap at 8
    l.push({ label, url, icon });
    saveShortcuts(l);
    renderShortcuts();
    mirrorShortcuts();
    closeShortcutModal();
  }
  // Mirror the shortcut set to the app so the browser's left side rail shows the
  // exact same favorite sites as the new-tab page.
  function mirrorShortcuts() {
    if (!bridge) return;
    const list = getShortcuts().map((s) => ({ Label: s.label, Url: s.url }));
    bridge.postMessage({ type: "settings.set", data: { NewTabShortcuts: list } });
  }
  document.getElementById("scClose").addEventListener("click", closeShortcutModal);
  document.getElementById("scCancel").addEventListener("click", closeShortcutModal);
  document.getElementById("scSave").addEventListener("click", saveShortcutModal);
  scModal.addEventListener("click", (e) => { if (e.target === scModal) closeShortcutModal(); });
  document.getElementById("scUrl").addEventListener("keydown", (e) => { if (e.key === "Enter") saveShortcutModal(); });
  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape" && !scModal.classList.contains("hidden")) closeShortcutModal();
  });

  // Quick links
  const ql = document.getElementById("quicklinks");
  QUICKLINKS.forEach((l) => {
    const a = document.createElement("a");
    a.href = l.url;
    a.textContent = l.label;
    ql.appendChild(a);
  });

  // ---- Search history (localStorage) ----
  function getHistory() {
    try { return JSON.parse(localStorage.getItem("pako-search-history")) || []; }
    catch { return []; }
  }
  function addHistory(term) {
    const t = term.trim();
    if (!t) return;
    let list = getHistory().filter((x) => x.toLowerCase() !== t.toLowerCase());
    list.unshift(t);
    if (list.length > 12) list = list.slice(0, 12);
    localStorage.setItem("pako-search-history", JSON.stringify(list));
  }
  function clearHistory() {
    localStorage.removeItem("pako-search-history");
    showHistory(); // keep the trending block visible if present
  }
  function removeHistory(term) {
    const t = String(term || "").toLowerCase();
    const next = getHistory().filter((x) => x.toLowerCase() !== t);
    localStorage.setItem("pako-search-history", JSON.stringify(next));
    showHistory();
  }
  function showHistory() {
    const list = getHistory();
    if (q.value.trim()) { historyEl.classList.add("hidden"); return; }

    // Trending on Niguro block (live keywords pulled from niguro.com via the app).
    const trendBox = document.getElementById("trendList");
    const trendHead = document.getElementById("trendHead");
    const recHead = document.getElementById("recHead");
    trendBox.innerHTML = "";
    const hasTrend = TRENDING.length > 0;
    trendHead.classList.toggle("hidden", !hasTrend);
    recHead.classList.toggle("hidden", !list.length);
    if (hasTrend) {
      TRENDING.slice(0, 8).forEach((t) => {
      const item = document.createElement("div");
      item.className = "history-item trend-item";
      item.innerHTML = `<span class="h-icon trend-ico">${ICON_TREND}</span><span class="h-text">${esc(t)}</span>`;
      item.addEventListener("click", (e) => {
        e.stopPropagation();
        q.value = t;
        doSearch(t);
      });
      trendBox.appendChild(item);
      });
    }

    const box = document.getElementById("historyList");
    box.innerHTML = "";
    list.slice(0, 4).forEach((t) => {
      const item = document.createElement("div");
      item.className = "history-item";
      item.innerHTML = `<span class="h-text">${esc(t)}</span><button type="button" class="h-del" title="Remove from history" aria-label="Remove ${esc(t)}">${ICON_X}</button>`;
      item.querySelector(".h-del").addEventListener("click", (e) => {
        e.stopPropagation();
        removeHistory(t);
      });
      item.addEventListener("click", (e) => {
        if (e.target.closest(".h-del")) return;
        e.stopPropagation();
        q.value = t;
        doSearch(t);
      });
      box.appendChild(item);
    });

    if (!list.length && !hasTrend) { historyEl.classList.add("hidden"); return; }
    historyEl.classList.remove("hidden");
    suggestEl.classList.add("hidden");
  }
  document.getElementById("clearHistory").addEventListener("click", clearHistory);
  clearQBtn.addEventListener("click", () => {
    q.value = "";
    q.focus();
    clearQBtn.classList.add("hidden");
    showHistory();
  });

  // ---- Search ----
  function looksLikeUrl(v) {
    if (/^https?:\/\//i.test(v)) return true;
    if (/^(localhost|(\d{1,3}\.){3}\d{1,3})(:\d+)?(\/.*)?$/.test(v)) return true;
    if (/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+(\/[^\s]*)?$/i.test(v)) return true;
    return false;
  }
  function normalizeUrl(v) { return /^https?:\/\//i.test(v) ? v : "https://" + v; }

  function doSearch(term) {
    const v = term.trim();
    if (!v) return;
    addHistory(v);
    if (looksLikeUrl(v)) { window.location.href = normalizeUrl(v); return; }
    window.location.href = searchUrlFor(engine, v);
  }

  // ---- Unified keyboard navigation (exactly like niguro.com's index search) ----
  // One list of rows spans whichever dropdown is showing: trending/recent (#history)
  // when empty, or suggestions (#suggest) while typing. Arrow keys walk ALL of them.
  let _typedVal = "";
  let _suppressInput = false; // prevent re-fetch during arrow-key value changes
  let _selIdx = -1;

  // Rows from whichever dropdown is visible, in DOM order.
  function visibleRows() {
    if (!suggestEl.classList.contains("hidden"))
      return [...suggestEl.querySelectorAll(".suggest-item")];
    if (!historyEl.classList.contains("hidden"))
      return [...historyEl.querySelectorAll(".history-item")];
    return [];
  }
  function rowText(el) {
    const t = el.querySelector(".s-text, .h-text");
    return t ? t.textContent : (el.textContent || "").trim();
  }
  // Highlight the currently selected row. mirror=true also copies its text into
  // the input (arrow keys, like niguro); hover just paints the highlight.
  function paintSelection(mirror) {
    const rows = visibleRows();
    rows.forEach((el, i) => {
      el.classList.toggle("active", i === _selIdx);
      if (i === _selIdx) el.scrollIntoView({ block: "nearest", behavior: "smooth" });
    });
    if (mirror) {
      if (_selIdx >= 0 && rows[_selIdx]) {
        _suppressInput = true;
        const v = rowText(rows[_selIdx]);
        if (v) q.value = v;
        q.setSelectionRange(q.value.length, q.value.length);
      } else if (_selIdx === -1 && q.value !== _typedVal) {
        _suppressInput = true;
        q.value = _typedVal;
      }
    }
  }
  function clearSelection() {
    _selIdx = -1;
    document.querySelectorAll(".suggest-item.active, .history-item.active").forEach((el) => el.classList.remove("active"));
  }

  q.addEventListener("focus", () => { _typedVal = q.value; _selIdx = -1; });
  q.addEventListener("keydown", (e) => {
    const rows = visibleRows();
    if (e.key === "Enter") {
      e.preventDefault();
      if (_selIdx >= 0 && rows[_selIdx]) doSearch(rowText(rows[_selIdx]));
      else doSearch(q.value);
    } else if (e.key === "ArrowDown") {
      e.preventDefault();
      if (rows.length) {
        _selIdx = (_selIdx + 1) % rows.length;   // wraps 0→1…→end→0 like niguro
        paintSelection(true);
      }
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      if (rows.length) {
        _selIdx = _selIdx <= 0 ? rows.length - 1 : _selIdx - 1; // wraps to end
        paintSelection(true);
      }
    } else if (e.key === "Escape") {
      suggestEl.classList.add("hidden");
      historyEl.classList.add("hidden");
      clearSelection();
      _suppressInput = true;
      q.value = _typedVal;
    }
  });
  q.addEventListener("input", () => {
    // Input events we triggered ourselves (mirroring a row's text into the box while
    // arrowing) must not rebuild the list or drop the current highlight.
    if (_suppressInput) { _suppressInput = false; return; }
    clearQBtn.classList.toggle("hidden", !q.value.trim());
    _typedVal = q.value; // keep track of the real typed text
    if (!q.value.trim()) { _typedVal = ""; showHistory(); return; }
    historyEl.classList.add("hidden");
    clearSelection();
    fetchSuggestions(q.value.trim());
  });
  q.addEventListener("focus", () => {
    // Like niguro.com's index: the dropdown only appears when the input is active.
    if (!q.value.trim()) showHistory();
  });
  // Close the dropdown when clicking anywhere outside the search area.
  document.addEventListener("click", (e) => {
    if (!e.target.closest(".search-wrap")) {
      suggestEl.classList.add("hidden");
      historyEl.classList.add("hidden");
    }
  });

  // ---- Autosuggest (JSONP like niguro.com's own search box) ----
  let suggestSeq = 0;
  function fetchSuggestions(query) {
    const eng = engInfo(engine);
    if (!eng.suggest) { suggestEl.classList.add("hidden"); return; }
    const seq = ++suggestSeq;
    const cbName = "pakoSuggest" + seq;
    window[cbName] = (data) => {
      if (seq !== suggestSeq) return;
      const list = data && data[1] ? data[1].slice(0, 4) : [];
      renderSuggestions(list);
      delete window[cbName];
    };
    const s = document.createElement("script");
    const base = eng.suggest.includes("{q}")
      ? eng.suggest.replace("{q}", encodeURIComponent(query))
      : eng.suggest + encodeURIComponent(query);
    s.src = base + "&callback=" + cbName;
    s.onerror = () => { if (seq === suggestSeq) suggestEl.classList.add("hidden"); delete window[cbName]; };
    setTimeout(() => { if (seq === suggestSeq) delete window[cbName]; }, 6000);
    document.head.appendChild(s);
  }
  function renderSuggestions(list) {
    suggestions = list;
    if (!list.length) { suggestEl.classList.add("hidden"); return; }
    suggestEl.innerHTML = "";
    list.forEach((item, i) => {
      const d = document.createElement("div");
      d.className = "suggest-item";
      d.innerHTML = `<span class="s-icon">${ICON_SEARCH}</span><span class="s-text">${esc(item)}</span><span class="s-caret">↵</span>`;
      d.addEventListener("click", (e) => { e.stopPropagation(); doSearch(item); });
      d.addEventListener("mousemove", () => { _selIdx = i; paintSelection(false); });
      suggestEl.appendChild(d);
    });
    suggestEl.classList.remove("hidden");
    historyEl.classList.add("hidden");
  }

  // ---- Wallpaper ----
  const WALLS = [
    { id: "g1", label: "Blue burst", css: "linear-gradient(160deg,#396afc 0%,#2948ff 100%)" },
    { id: "g2", label: "Sunset", css: "linear-gradient(160deg,#eb3349 0%,#f45c43 100%)" },
    { id: "g3", label: "Deep sky", css: "linear-gradient(160deg,#091e3a 0%,#2f80ed 50%,#2d9ee0 100%)" },
    { id: "g4", label: "Pink fusion", css: "linear-gradient(160deg,#ec008c 0%,#fc6767 100%)" },
    { id: "g5", label: "Berry dusk", css: "linear-gradient(160deg,#c04848 0%,#480048 100%)" },
    { id: "g6", label: "Mint calm", css: "linear-gradient(160deg,#81ff8a 0%,#64965e 100%)" },
    { id: "g7", label: "Lagoon", css: "linear-gradient(160deg,#59cde9 0%,#0a2a88 100%)" },
    { id: "g8", label: "Slate", css: "linear-gradient(160deg,#0f2027 0%,#2c5364 100%)" },
  ];
  function currentWall() { return localStorage.getItem("pako-wall") || ""; }
  // Mirror any user wallpaper change back to the app so the full Settings page
  // (Background wallpaper) and this page always agree.
  function syncWall(v) {
    if (bridge) bridge.postMessage({ type: "settings.set", data: { NewTabWallpaper: v } });
  }
  let wallSeq = 0;
  function applyWallpaper(wall, persist = true) {
    const b = document.body;
    b.classList.add("art");
    b.style.cssText = ""; // clear any previous inline background first
    if (!wall) {
      b.classList.remove("art");
    } else if (wall.startsWith("gradient:")) {
      const g = WALLS.find((w) => w.id === wall.replace("gradient:", ""));
      if (g) b.style.background = g.css;
    } else {
      // Image URL: preload first, then apply longhands. Never set .background = ""
      // afterwards — an empty shorthand wipes background-image, so the wallpaper
      // would silently never show. Fall back to a gradient if the image fails.
      const seq = ++wallSeq;
      const img = new Image();
      img.onload = () => {
        if (seq !== wallSeq) return;
        b.style.backgroundImage = `url('${wall}')`;
        b.style.backgroundSize = "cover";
        b.style.backgroundPosition = "center";
        b.style.backgroundAttachment = "scroll";
      };
      img.onerror = () => {
        if (seq === wallSeq) b.style.background = WALLS[0].css;
      };
      img.src = wall;
    }
    if (persist) localStorage.setItem("pako-wall", wall);
    renderWallModal();
    updateLogo(); // wallpaper mode uses the dark logo
  }
  function renderWallModal() {
    const cur = currentWall();
    document.querySelectorAll(".wall-tile").forEach((t) => {
      t.classList.toggle("active", cur === "gradient:" + t.dataset.wall);
    });
  }
  const wallModal = document.getElementById("wallModal");
  const wallGrid = document.getElementById("wallPresets");
  WALLS.forEach((w) => {
    const t = document.createElement("div");
    t.className = "wall-tile";
    t.dataset.wall = w.id;
    t.style.background = w.css;
    t.title = w.label;
    t.addEventListener("click", () => { applyWallpaper("gradient:" + w.id); syncWall("gradient:" + w.id); });
    wallGrid.appendChild(t);
  });
  document.getElementById("wallBtn").addEventListener("click", () => { renderWallModal(); wallModal.classList.remove("hidden"); });
  document.getElementById("wallClose").addEventListener("click", () => wallModal.classList.add("hidden"));
  wallModal.addEventListener("click", (e) => { if (e.target === wallModal) wallModal.classList.add("hidden"); });
  document.getElementById("wallApply").addEventListener("click", () => {
    const url = document.getElementById("wallUrl").value.trim();
    if (url) { applyWallpaper(url); syncWall(url); }
    wallModal.classList.add("hidden");
  });
  document.getElementById("wallNone").addEventListener("click", () => {
    applyWallpaper("");
    syncWall("");
    wallModal.classList.add("hidden");
  });

  // ---- Free wallpaper search — Wikimedia Commons (no key needed) ----
  const wallSuggest = document.getElementById("wallSuggest");
  const wallKeyword = document.getElementById("wallKeyword");
  function pickWall(full) {
    applyWallpaper(full);
    syncWall(full);
    wallModal.classList.add("hidden");
  }
  function searchWalls() {
    const kw = wallKeyword.value.trim();
    if (!kw) return;
    const seq = ++wallSeq;
    wallSuggest.innerHTML = `<div style="grid-column:1/-1;text-align:center;color:var(--muted);font-size:12.5px;padding:14px 0">Searching Wikimedia Commons…</div>`;
    const api = "https://commons.wikimedia.org/w/api.php?action=query&format=json&origin=*"
      + "&generator=search&gsrnamespace=6&gsrlimit=12"
      + "&gsrsearch=" + encodeURIComponent("filetype:bitmap " + kw)
      + "&prop=imageinfo&iiprop=url|mime|size&iiurlwidth=1920";
    fetch(api)
      .then((r) => r.json())
      .then((j) => {
        if (seq !== wallSeq) return;
        const pages = (j.query && j.query.pages) ? Object.values(j.query.pages) : [];
        wallSuggest.innerHTML = "";
        let shown = 0;
        pages.forEach((p) => {
          if (shown >= 8) return;
          const ii = p.imageinfo && p.imageinfo[0];
          if (!ii) return;
          const mime = (ii.mime || "").toLowerCase();
          if (mime !== "image/jpeg" && mime !== "image/png") return;
          const w = ii.width || 0, h = ii.height || 0;
          if (w < 1200 || h < 600) return; // too small to be a wallpaper
          const full = ii.thumburl || ii.url;
          const thumb = ii.thumburl ? ii.thumburl.replace(/\/\d+px-/, "/400px-") : ii.url;
          const t = document.createElement("div");
          t.className = "wall-tile";
          t.style.backgroundImage = `url('${thumb}')`;
          t.title = (p.title || "Apply wallpaper").replace(/^File:/, "").replace(/_/g, " ");
          t.addEventListener("click", () => pickWall(full));
          wallSuggest.appendChild(t);
          shown++;
        });
        if (shown === 0) wallSuggest.innerHTML = `<div style="grid-column:1/-1;text-align:center;color:var(--muted);font-size:12.5px;padding:14px 0">No photos found for “${kw}” — try another keyword.</div>`;
      })
      .catch(() => {
        if (seq === wallSeq) wallSuggest.innerHTML = `<div style="grid-column:1/-1;text-align:center;color:var(--muted);font-size:12.5px;padding:14px 0">Search failed — check your connection.</div>`;
      });
  }
  document.getElementById("wallSearch").addEventListener("click", searchWalls);
  wallKeyword.addEventListener("keydown", (e) => { if (e.key === "Enter") searchWalls(); });

  // ---- Customize modal: Background / Widgets tabs ----
  const custTabs = document.querySelectorAll(".cust-tab");
  const custPanes = {
    bg: document.getElementById("custBgPane"),
    widgets: document.getElementById("custWidgetPane"),
  };
  custTabs.forEach((t) => t.addEventListener("click", () => {
    custTabs.forEach((x) => x.classList.toggle("on", x === t));
    for (const k in custPanes) custPanes[k].classList.toggle("hidden", k !== t.dataset.tab);
  }));
  // Widget toggles → app settings (the full Settings page listens to the same keys).
  const WIDGET_KEYS = {
    wShowShortcuts: "ShowShortcuts",
    wShowDate: "NewTabShowDate",
    wShowQuickLinks: "NewTabShowQuickLinks",
    wShowEvents: "NewTabShowEvents",
    wShowNews: "NewTabShowNews",
  };
  function syncWidgetToggles(s) {
    if (!s) return;
    for (const id in WIDGET_KEYS) {
      const el = document.getElementById(id);
      if (!el) continue;
      const v = s[WIDGET_KEYS[id]];
      if (typeof v === "boolean") el.classList.toggle("on", v);
    }
  }
  for (const id in WIDGET_KEYS) {
    const el = document.getElementById(id);
    if (!el) continue;
    el.addEventListener("click", () => {
      const on = !el.classList.contains("on");
      el.classList.toggle("on", on);
      if (bridge) bridge.postMessage({ type: "settings.set", data: { [WIDGET_KEYS[id]]: on } });
    });
  }

  // ---- News strip (bhitta-style horizontal feed, from Pako News) ----
  function renderNews() {
    const list = document.getElementById("newsList");
    if (!list) return;
    const items = NEWS.filter((i) => newsLang === "all" || i.Lang === newsLang).slice(0, 10);
    list.innerHTML = items.map((it) => {
      const media = it.Image
        ? `<img loading="lazy" src="${esc(it.Image)}" referrerpolicy="no-referrer" alt="" onerror="this.remove()">`
        : `<span class="n-ph">${esc((it.Source || "?").charAt(0).toUpperCase())}</span>`;
      return `<a class="news-card" href="${esc(it.Link)}" target="_blank" rel="noopener">` +
        `<div class="n-media">${media}</div>` +
        `<div class="n-body"><span class="n-src">${esc(it.Source || "")}</span>` +
        `<span class="n-title">${esc(it.Title || "")}</span></div></a>`;
    }).join("");
    updateNewsArrows();
  }
  document.querySelectorAll("#newsLang .pill").forEach((b) => b.addEventListener("click", () => {
    newsLang = b.dataset.lang;
    document.querySelectorAll("#newsLang .pill").forEach((x) => x.classList.toggle("on", x === b));
    renderNews();
  }));
  document.getElementById("newsMore").addEventListener("click", () => {
    if (bridge) bridge.postMessage({ type: "nav.news" });
  });

  // Scroll arrows: appear only on the side that still has content to reveal, and
  // only while the strip is hovered (like a cover-flow / feed rail).
  const newsListEl = document.getElementById("newsList");
  const newsPrev = document.getElementById("newsPrev");
  const newsNext = document.getElementById("newsNext");
  function updateNewsArrows() {
    if (!newsListEl) return;
    const max = newsListEl.scrollWidth - newsListEl.clientWidth;
    const x = newsListEl.scrollLeft;
    newsPrev?.classList.toggle("show", x > 4 && max > 0);
    newsNext?.classList.toggle("show", x < max - 4 && max > 0);
  }
  function newsScrollBy(dir) {
    if (!newsListEl) return;
    const card = newsListEl.querySelector(".news-card");
    const step = card ? card.offsetWidth + 12 : 232;
    newsListEl.scrollBy({ left: dir * step, behavior: "smooth" });
  }
  newsPrev?.addEventListener("click", () => newsScrollBy(-1));
  newsNext?.addEventListener("click", () => newsScrollBy(1));
  newsListEl?.addEventListener("scroll", updateNewsArrows, { passive: true });
  window.addEventListener("resize", updateNewsArrows);
  // Re-evaluate after a render (new cards change the overflow) and at boot.
  const newsArrowTimer = setInterval(updateNewsArrows, 900);
  window.addEventListener("unload", () => clearInterval(newsArrowTimer));

  ask("news.get"); // load the strip on boot (cached ~10 min in the app)

  // ---- Boot ----
  applyWallpaper(currentWall(), false);
  updatePlaceholder();
  updateLogo();
  renderShortcuts();
  mirrorShortcuts(); // tell the browser's side rail about the current set

  function esc(s) {
    return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
  }
})();