/* 游民导航 · 小红书小工具(纯离线 classic script) */ (function () { "use strict"; var data = window.NAV_DATA || {}; var categories = data.categories || []; var state = { theme: "light", activeId: categories[0] ? categories[0].id : "", drawerOpen: false, sheetOpen: false, currentLink: null, jumping: false, }; var els = {}; function $(id) { return document.getElementById(id); } function qs(sel, root) { return (root || document).querySelector(sel); } function escapeHtml(str) { return String(str == null ? "" : str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function applySafeArea() { try { var root = document.documentElement; var probe = document.createElement("div"); probe.style.cssText = "position:absolute;visibility:hidden;pointer-events:none;padding-top:constant(safe-area-inset-top);padding-top:env(safe-area-inset-top);"; document.body.appendChild(probe); var envTop = parseFloat(window.getComputedStyle(probe).paddingTop) || 0; document.body.removeChild(probe); // 小红书宿主顶栏(返回 / 分享)常盖住内容;Android 上 env 常为 0 var status = envTop > 0 ? envTop : 28; var hostBar = 44; var padTop = Math.max(status + hostBar, 72); root.style.setProperty("--app-pad-top", padTop + "px"); root.style.setProperty("--safe-area-inset-top", status + "px"); } catch (e) { document.documentElement.style.setProperty("--app-pad-top", "80px"); } } function readTheme() { try { var saved = localStorage.getItem("navmini-xhs-theme"); if (saved === "light" || saved === "dark") return saved; } catch (e) {} return "light"; } function setTheme(theme) { state.theme = theme; document.documentElement.setAttribute("data-theme", theme); try { localStorage.setItem("navmini-xhs-theme", theme); } catch (e) {} if (els.themeIcon) { els.themeIcon.textContent = theme === "dark" ? "☀" : "☾"; } } function showToast(msg) { if (!els.toast) return; els.toast.textContent = msg; els.toast.classList.add("show"); clearTimeout(showToast._t); showToast._t = setTimeout(function () { els.toast.classList.remove("show"); }, 1600); } /** 选中网址文本(容器禁用程序化写入剪贴板,仅做文本选中) */ function selectUrlText() { try { var node = els.sheetUrlBox; if (!node) return false; var range = document.createRange(); range.selectNodeContents(node); var sel = window.getSelection(); if (!sel) return false; sel.removeAllRanges(); sel.addRange(range); return true; } catch (e) { return false; } } function setGuideStep(step) { var items = els.guideSteps ? els.guideSteps.querySelectorAll(".guide-step") : []; for (var i = 0; i < items.length; i++) { var n = i + 1; items[i].classList.toggle("is-active", n === step); items[i].classList.toggle("is-done", n < step); } } function setGuideUi(phase) { // phase: idle | selected var selected = phase === "selected"; if (els.sheetUrlBox) { els.sheetUrlBox.classList.toggle("is-pulse", !selected); els.sheetUrlBox.classList.toggle("is-selected", selected); } if (els.sheetSelect) { els.sheetSelect.classList.toggle("pulse", !selected); els.sheetSelect.classList.toggle("is-done", selected); } if (els.sheetSelectLabel) { els.sheetSelectLabel.textContent = selected ? "② 已选中 · 再点可重选" : "① 点我选中网址"; } if (els.sheetSelectSub) { els.sheetSelectSub.textContent = selected ? "下一步:手指按住蓝色网址区" : "选中后请按住网址复制"; } // 第一步:只显示向下指按钮的小手;第二步:小手上移指网址,文案在右边 if (els.fingerBtn) { els.fingerBtn.classList.toggle("is-hidden", selected); els.fingerBtn.setAttribute("aria-hidden", selected ? "true" : "false"); } if (els.fingerUrl) { els.fingerUrl.classList.toggle("is-hidden", !selected); els.fingerUrl.setAttribute("aria-hidden", selected ? "false" : "true"); } if (els.sheetUrlWrap) { els.sheetUrlWrap.classList.toggle("is-pointing", selected); } setGuideStep(selected ? 2 : 1); } function findCategory(id) { for (var i = 0; i < categories.length; i++) { if (categories[i].id === id) return categories[i]; } return categories[0] || null; } function countLinks(cat) { var n = 0; var groups = cat.groups || []; for (var i = 0; i < groups.length; i++) { n += (groups[i].links || []).length; } return n; } function renderDrawer() { var html = ""; for (var i = 0; i < categories.length; i++) { var cat = categories[i]; var active = cat.id === state.activeId ? " active" : ""; html += '' + '' + escapeHtml(cat.emoji || "·") + "" + '' + escapeHtml(cat.title) + "" + '' + countLinks(cat) + "" + ""; } els.drawerNav.innerHTML = html; } function renderContent() { var html = ""; for (var i = 0; i < categories.length; i++) { var cat = categories[i]; html += '' + '' + escapeHtml(cat.title) + '' + countLinks(cat) + ""; var groups = cat.groups || []; for (var g = 0; g < groups.length; g++) { var group = groups[g]; html += '' + escapeHtml(group.title) + ''; var links = group.links || []; for (var l = 0; l < links.length; l++) { var link = links[l]; html += '' + '' + '' + escapeHtml(link.title) + '' + escapeHtml(link.subtitle || "") + ""; } html += ""; } html += ""; } els.content.innerHTML = html; } function setDrawer(open) { state.drawerOpen = open; els.drawer.classList.toggle("open", open); els.drawer.setAttribute("aria-hidden", open ? "false" : "true"); document.body.style.overflow = open || state.sheetOpen ? "hidden" : ""; } function setSheet(open) { state.sheetOpen = open; els.sheet.classList.toggle("open", open); els.sheet.setAttribute("aria-hidden", open ? "false" : "true"); document.body.style.overflow = open || state.drawerOpen ? "hidden" : ""; } function findLink(catId, groupId, linkId) { var cat = findCategory(catId); if (!cat) return null; var groups = cat.groups || []; for (var i = 0; i < groups.length; i++) { if (groups[i].id !== groupId) continue; var links = groups[i].links || []; for (var j = 0; j < links.length; j++) { if (links[j].id === linkId) return links[j]; } } return null; } function openLink(link) { // 容器禁止打开外链 / 跳转其他小工具(见能力清单 §4.2),一律走复制引导 openLinkSheet(link); } function openLinkSheet(link) { state.currentLink = link; els.sheetIcon.src = link.icon || "./icons/_brand.webp"; els.sheetTitle.textContent = link.title || ""; els.sheetSub.textContent = link.subtitle || ""; els.sheetUrlBox.textContent = link.href || ""; els.sheetStatus.textContent = "按步骤操作:先点绿色按钮选中网址"; els.sheetStatus.classList.remove("is-error"); setGuideUi("idle"); setSheet(true); } function markSelected() { if (!state.currentLink) return; if (selectUrlText()) { setGuideUi("selected"); els.sheetStatus.textContent = "已选中蓝色区域 · 请用手指按住网址,在菜单里点「复制」"; els.sheetStatus.classList.remove("is-error"); showToast("已选中,请按住复制"); // 短暂强调第 3 步 setTimeout(function () { setGuideStep(3); var s2 = els.guideSteps && els.guideSteps.querySelector('[data-step="2"]'); if (s2) s2.classList.add("is-done"); }, 1600); } else { setGuideUi("idle"); els.sheetStatus.textContent = "自动选中失败,请手指直接按住蓝色网址文字"; els.sheetStatus.classList.add("is-error"); setGuideStep(2); } } function jumpToCategory(id) { state.activeId = id; setPill(); renderDrawer(); setDrawer(false); state.jumping = true; var el = document.getElementById("cat-" + id); if (el) { el.scrollIntoView({ behavior: "smooth", block: "start" }); } setTimeout(function () { state.jumping = false; }, 700); } function setPill() { var cat = findCategory(state.activeId); if (els.brandCurrent && cat) { els.brandCurrent.textContent = cat.title; } } function onScroll() { var y = window.scrollY || document.documentElement.scrollTop || 0; els.backTop.classList.toggle("show", y > 480); if (state.jumping) return; var marker = 100; var current = categories[0] ? categories[0].id : ""; for (var i = 0; i < categories.length; i++) { var el = document.getElementById("cat-" + categories[i].id); if (!el) continue; if (el.getBoundingClientRect().top <= marker) { current = categories[i].id; } else { break; } } if (current && current !== state.activeId) { state.activeId = current; setPill(); renderDrawer(); } } function bind() { els.btnMenu.addEventListener("click", function () { setDrawer(true); }); els.drawerMask.addEventListener("click", function () { setDrawer(false); }); els.drawerClose.addEventListener("click", function () { setDrawer(false); }); els.btnTheme.addEventListener("click", function () { setTheme(state.theme === "dark" ? "light" : "dark"); }); els.backTop.addEventListener("click", function () { window.scrollTo({ top: 0, behavior: "smooth" }); }); els.drawerNav.addEventListener("click", function (e) { var btn = e.target.closest(".drawer-item"); if (!btn) return; jumpToCategory(btn.getAttribute("data-cat")); }); els.content.addEventListener("click", function (e) { var btn = e.target.closest(".link-card"); if (!btn) return; var link = findLink( btn.getAttribute("data-cat"), btn.getAttribute("data-group"), btn.getAttribute("data-id") ); if (link) openLink(link); }); els.sheetMask.addEventListener("click", function () { setSheet(false); }); els.sheetClose.addEventListener("click", function () { setSheet(false); }); els.sheetSelect.addEventListener("click", function () { markSelected(); }); els.sheetUrlBox.addEventListener("click", function () { markSelected(); }); // 用户开始触碰网址区时,推进到第 2/3 步提示 els.sheetUrlBox.addEventListener("touchstart", function () { if (els.sheetUrlBox.classList.contains("is-selected")) { setGuideStep(3); var s2 = els.guideSteps && els.guideSteps.querySelector('[data-step="2"]'); if (s2) s2.classList.add("is-done"); els.sheetStatus.textContent = "按住不放,在弹出菜单中点「复制」"; } }, { passive: true }); window.addEventListener("scroll", onScroll, { passive: true }); } function init() { els = { brandName: $("brand-name"), brandCurrent: $("brand-current"), brandLogo: $("brand-logo"), footer: $("footer"), content: $("content"), drawer: $("drawer"), drawerNav: $("drawer-nav"), drawerMask: $("drawer-mask"), drawerClose: $("drawer-close"), drawerBrand: $("drawer-brand"), drawerSub: $("drawer-sub"), btnMenu: $("btn-menu"), btnTheme: $("btn-theme"), themeIcon: $("theme-icon"), backTop: $("back-top"), sheet: $("sheet"), sheetMask: $("sheet-mask"), sheetIcon: $("sheet-icon"), sheetTitle: $("sheet-title"), sheetSub: $("sheet-sub"), sheetUrlBox: $("sheet-url-box"), sheetStatus: $("sheet-status"), sheetSelect: $("sheet-select"), sheetSelectLabel: $("sheet-select-label"), sheetSelectSub: $("sheet-select-sub"), sheetClose: $("sheet-close"), guideSteps: $("guide-steps"), sheetUrlWrap: $("sheet-url-wrap"), fingerBtn: $("finger-btn"), fingerUrl: $("finger-url"), toast: $("toast"), }; applySafeArea(); setTheme(readTheme()); var brand = data.brand || "游民导航"; els.brandName.textContent = brand; els.footer.textContent = data.footer || ""; els.drawerBrand.textContent = brand; els.drawerSub.textContent = data.tagline || ""; if (data.brandIcon && els.brandLogo) { els.brandLogo.src = data.brandIcon; } renderDrawer(); renderContent(); setPill(); bind(); onScroll(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();