https://greasyfork.org/zh-CN/scripts/605137-middlefun-domain-name-tool
// ==UserScript==
// @name MiddleFun 域名信息放大镜
// @namespace yee.cx
// @version 1.0.1
// @description 在 MiddleFun(middlefun.com)帖子正文与回复中自动识别外链域名,域名右侧显示放大镜图标,点击弹出该域名的基础信息(注册状态 / 注册商 / 注册时间 / 到期时间),数据来自 yee.cx
// @author Evan
// @match https://middlefun.com/*
// @match https://www.middlefun.com/*
// @grant GM_xmlhttpRequest
// @connect yee.cx
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
var API_ENRICH = 'https://yee.cx/api/enrich';
var API_WHOIS = 'https://yee.cx/api/whois';
var BASE_URL = 'https://yee.cx/';
var CACHE = Object.create(null); // 域名 -> Promise
// 站内域名不插放大镜(MiddleFun 站内链接本身就是相对路径,这里兜底绝对链接)
var SKIP_HOSTS = { 'middlefun.com': 1, 'www.middlefun.com': 1 };
var SKIP_TLDS = { local: 1, localhost: 1, test: 1, invalid: 1, example: 1, lan: 1, internal: 1, onion: 1 };
// 真实存在的顶级域(ccTLD 全量 + 常见 gTLD),用于把 "default.They" 这类句子误判挡掉
var TLD_SET = Object.create(null);
('ac ad ae af ag ai al am ao aq ar as at au aw ax az ba bb bd be bf bg bh bi bj bm bn bo bq br bs bt bw by bz ca cc cd cf cg ch ci ck cl cm cn co cr cu cv cw cx cy cz de dj dk dm do dz ec ee eg er es et eu fi fj fk fm fo fr ga gb gd ge gf gg gh gi gl gm gn gp gq gr gs gt gu gw gy hk hm hn hr ht hu id ie il im in io iq ir is it je jm jo jp ke kg kh ki km kn kp kr kw ky kz la lb lc li lk lr ls lt lu lv ly ma mc md me mg mh mk ml mm mn mo mp mq mr ms mt mu mv mw mx my mz na nc ne nf ng ni nl no np nr nu nz om pa pe pf pg ph pk pl pm pn pr ps pt pw py qa re ro rs ru rw sa sb sc sd se sg sh si sk sl sm sn so sr ss st su sv sx sy sz tc td tf tg th tj tk tl tm tn to tr tt tv tw tz ua ug uk us uy uz va vc ve vg vi vn vu wf ws ye yt za zm zw ' +
'com net org info biz name pro mobi asia tel travel xxx aero coop museum jobs cat post int gov edu mil arpa online site shop store tech space website xyz top club vip win bid loan men date review party trade science racing cricket webcam accountant faith link live life world today news blog press host cloud one run zone city town global digital agency company center solutions network systems software studio design media photo pics video tube social email group team work works services support tools dev app page art fun icu buzz wiki cyou sbs cf loves click fit lol mom best cool plus ink gold luxury casa cafe bar pub restaurant hotel farm garden money finance bank insurance credit loans market exchange capital fund trading business enterprise industries limited llc ltd inc guru ninja rocks wang xin ren style fashion beauty care health dental clinic doctor lawyer attorney accountant tax realestate properties rentals apartments houses estate construction engineering energy solar power water green eco bio food kitchen recipes wine beer coffee pizza sushi gift toys games play sport fitness yoga dance music band radio movies film theater tickets events holidays tours cruises flights airport taxi cars auto bikes motorcycles trucks boats yachts sea beach island mountain camp outdoor flowers plants pets vet jewelry watches shoes clothes dress boutique salons spa massage tattoo photography graphics gallery auctions deals discount sale cheap free coupon cash card pay crypto bitcoin coin token chain blocks wallet fintech domain domains dns mail chat forum bbs wiki press streaming server servers vps panel cdn data database code apps security vpn proxy game').split(/\s+/).forEach(function (t) { if (t) TLD_SET[t] = 1; });
// 常见文件名后缀:xxx.png / xxx.zip 之类的图片、压缩包、文档、脚本一律不当域名
var FILE_EXT = Object.create(null);
('png jpg jpeg gif webp svg ico bmp tif tiff avif heic heif mp4 webm mov avi mkv flv wmv m4v mp3 wav flac ogg m4a aac opus zip rar tar gz bz2 xz pack pdf doc docx xls xlsx ppt pptx rtf odt ods json xml yml yaml js mjs css scss html htm php asp aspx jsp cgi bat exe msi dmg pkg apk ipa deb rpm iso img bin dat sql db lock log woff woff2 ttf otf eot csv tsv conf ini env kt swift dart vue jsx tsx bak tmp swp srt ass vtt').split(/\s+/).forEach(function (t) { if (t) FILE_EXT[t] = 1; });
// 英文常用词:未命中 TLD 白名单时,含这些词的串基本是句子而非域名(如 "default.They")
var STOP_WORDS = Object.create(null);
('the a an and or but if then than that this these those there here when where which who whom whose why how what your you yours our ours their theirs its his her she he they them we us i my me is are was were be been being have has had do does did will would can could should may might must not no yes all any both each few more most other some such only own same very just too also well even still yet so as at by for from into of off on out over per to up via with within without about above after again against below between during before under until upon while default theyre youll dont doesnt didnt cant wont isnt wasnt arent').split(/\s+/).forEach(function (t) { if (t) STOP_WORDS[t] = 1; });
/* ---------------- 工具函数 ---------------- */
function isPlainDomain(d) {
if (!d) return false;
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(d)) return false; // 纯 IP
var parts = d.split('.');
var tld = parts[parts.length - 1];
if (!tld || tld.length < 2 || tld.length > 24) return false;
if (SKIP_TLDS[tld]) return false;
if (FILE_EXT[tld]) return false; // 文件名而非域名(screenshot.png 等)
if (TLD_SET[tld]) return true;
// 未收录的 TLD:只放行 2-3 位的生僻/新后缀,且整串不能含英文常用词
if (tld.length > 3) return false;
for (var i = 0; i < parts.length; i++) {
if (STOP_WORDS[parts[i]]) return false;
}
return true;
}
// 常见多级公共后缀(含中文圈常用的免费社区后缀,如 eu.org / dpdns.org / us.kg)
var PUBLIC_SUFFIXES = [
'com.cn', 'net.cn', 'org.cn', 'gov.cn', 'edu.cn', 'ac.cn',
'com.ee', 'pri.ee', 'org.ee',
'co.uk', 'org.uk', 'me.uk', 'ac.uk', 'gov.uk',
'com.au', 'net.au', 'org.au',
'co.jp', 'ne.jp', 'or.jp', 'ac.jp', 'go.jp',
'com.hk', 'com.tw', 'org.tw', 'com.kr', 'co.kr', 'or.kr',
'com.sg', 'com.my', 'com.vn', 'co.nz', 'com.br', 'com.mx',
'com.tr', 'com.co', 'co.in', 'ac.in', 'com.ph', 'com.ua',
'com.pl', 'com.ru', 'com.ar', 'com.pe', 'co.id', 'com.th',
'eu.org', 'dpdns.org', 'us.kg', 'qzz.io', 'rr.nu', 'nyc.mn'
];
// 匹配最长公共后缀(多级后缀优先)
function matchPublicSuffix(d) {
var best = '';
for (var i = 0; i < PUBLIC_SUFFIXES.length; i++) {
var s = PUBLIC_SUFFIXES[i];
if (s.length > best.length && d.slice(-(s.length + 1)) === '.' + s) best = s;
}
return best;
}
// 判断是否为真子域(不支持 yee.cx 查询):
// 域名去掉末尾公共后缀后,剩余标签恰好 1 个即为注册域(允许查询),>1 才判定为真子域
function isSubDomain(d) {
if (!d) return false;
var ps = matchPublicSuffix(d);
var rest = ps ? d.slice(0, d.length - ps.length - 1) : d.slice(0, d.lastIndexOf('.'));
if (!rest) return false;
return rest.split('.').length > 1;
}
function normalizeDomain(d) {
var v = String(d || '').toLowerCase();
v = v.replace(/^https?:\/\//i, '').replace(/[\/?#].*$/, '').trim();
if (v.indexOf('www.') === 0) v = v.slice(4);
try {
var host = new URL('http://' + v).hostname;
if (host && host.indexOf('.') > 0) v = host;
} catch (e) {}
return v;
}
function esc(s) {
var d = document.createElement('div');
d.textContent = s == null ? '' : String(s);
return d.innerHTML;
}
/* ---------------- 图标(沿用站点 lucide 图标风格:stroke-width 2) ---------------- */
var MAG_SVG = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>';
var ARROW_SVG = '<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 17 17 7"/><path d="M7 7h10v10"/></svg>';
// 用 span + role=button 而非 <a>:避免被站内 .prose a 的下划线/绿色链接样式命中,也不受 CSP 限制
function makeMagnifier(domain) {
var el = document.createElement('span');
el.className = 'nlc-mag';
el.setAttribute('role', 'button');
el.setAttribute('tabindex', '0');
el.title = '查询域名信息:' + domain;
el.setAttribute('aria-label', '查询域名信息:' + domain);
el.setAttribute('data-nlc-domain', domain);
el.innerHTML = MAG_SVG;
var open = function (e) {
if (e) { e.preventDefault(); e.stopPropagation(); }
showPopup(domain, el);
};
el.addEventListener('click', open);
el.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') open(e);
});
return el;
}
/* ---------------- 小窗 ---------------- */
var activePopup = null;
function closePopup() {
if (activePopup) {
if (activePopup.parentNode) activePopup.parentNode.removeChild(activePopup);
activePopup = null;
}
document.removeEventListener('keydown', onDocKey);
}
function onDocKey(e) {
if (e.key === 'Escape') closePopup();
}
function showPopup(domain, anchor) {
closePopup();
var pop = document.createElement('div');
pop.className = 'nlc-pop';
var head = document.createElement('div');
head.className = 'nlc-pop-head';
var title = document.createElement('span');
title.className = 'nlc-pop-title';
title.innerHTML = '<span class="nlc-pop-tip">' + MAG_SVG + '</span>' + esc(domain);
var closeBtn = document.createElement('button');
closeBtn.className = 'nlc-pop-close';
closeBtn.type = 'button';
closeBtn.setAttribute('aria-label', '关闭');
closeBtn.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>';
closeBtn.addEventListener('click', closePopup);
head.appendChild(title);
head.appendChild(closeBtn);
var body = document.createElement('div');
body.className = 'nlc-pop-body';
body.innerHTML = '<div class="nlc-loading"><span class="nlc-spinner"></span>加载中…</div>';
var foot = document.createElement('div');
foot.className = 'nlc-pop-foot';
var more = document.createElement('a');
more.className = 'nlc-pop-more';
more.href = BASE_URL + encodeURIComponent(domain);
more.target = '_blank';
more.rel = 'noopener noreferrer';
more.innerHTML = '在 yee.cx 查看完整 Whois' + ARROW_SVG;
foot.appendChild(more);
pop.appendChild(head);
pop.appendChild(body);
pop.appendChild(foot);
document.body.appendChild(pop);
// 定位:图标下方,视口溢出则翻转
var r = anchor.getBoundingClientRect();
var pw = pop.offsetWidth;
var ph = pop.offsetHeight;
var left = r.left;
var maxLeft = window.innerWidth - pw - 8;
if (left > maxLeft) left = maxLeft;
if (left < 8) left = 8;
var top = r.bottom + 6;
if (top + ph > window.innerHeight - 8) top = r.top - ph - 6;
if (top < 8) top = 8;
pop.style.left = left + 'px';
pop.style.top = top + 'px';
activePopup = pop;
document.addEventListener('keydown', onDocKey);
if (isSubDomain(domain)) {
body.innerHTML = '<div class="nlc-err">不支持二级域名查询</div>';
} else {
fetchEnrich(domain).then(function (data) {
if (activePopup !== pop) return;
renderBody(body, domain, data);
}).catch(function (err) {
if (activePopup !== pop) return;
// 降级:enrich 失败后尝试 whois 原始数据
fetchWhois(domain).then(function (raw) {
if (activePopup !== pop) return;
var w = parseWhoisRaw(raw);
var lower = String(raw).toLowerCase();
var isFree = /no match|not found|no data found|domain not found/.test(lower);
if (w.registrar || w.registered || w.expires) {
body.innerHTML = buildRows(domain, isFree ? 'free' : 'registered', w.registrar, w.registered, w.expires);
} else {
body.innerHTML = '<div class="nlc-err">查询失败</div>';
}
}).catch(function (e2) {
if (activePopup !== pop) return;
body.innerHTML = '<div class="nlc-err">查询失败</div>';
if (e2) console.error('[nlc-magnifier]', domain, e2);
});
if (err) console.error('[nlc-magnifier]', domain, err);
});
}
setTimeout(function () {
var detached = false;
var detach = function () {
if (detached) return;
detached = true;
document.removeEventListener('click', onDocClick);
window.removeEventListener('scroll', onDocScroll, true);
window.removeEventListener('resize', onDocResize);
};
var onDocClick = function (e) {
if (activePopup && !activePopup.contains(e.target) &&
!(e.target.closest && e.target.closest('.nlc-mag'))) closePopup();
detach();
};
// Next.js 单页路由 + 长帖滚动时会重排内容,滚动 / 缩放时直接收起弹窗
var onDocScroll = function () { closePopup(); detach(); };
var onDocResize = function () { closePopup(); detach(); };
document.addEventListener('click', onDocClick);
window.addEventListener('scroll', onDocScroll, true);
window.addEventListener('resize', onDocResize);
}, 0);
}
function fetchEnrich(domain) {
if (CACHE[domain]) return CACHE[domain];
CACHE[domain] = new Promise(function (resolve, reject) {
GM_xmlhttpRequest({
method: 'POST',
url: API_ENRICH,
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ domain: domain }),
timeout: 12000,
onload: function (res) {
var d;
try { d = JSON.parse(res.responseText); } catch (e) { reject(e); return; }
if (d && d.success && d.data) resolve(d.data);
else reject(new Error((d && d.error) || 'empty response'));
},
onerror: reject,
ontimeout: function () { reject(new Error('timeout')); }
});
});
return CACHE[domain];
}
// 降级:whois 原始数据接口
function fetchWhois(domain) {
var key = 'whois:' + domain;
if (CACHE[key]) return CACHE[key];
CACHE[key] = new Promise(function (resolve, reject) {
GM_xmlhttpRequest({
method: 'POST',
url: API_WHOIS,
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ domain: domain }),
timeout: 12000,
onload: function (res) {
var d;
try { d = JSON.parse(res.responseText); } catch (e) { reject(e); return; }
if (d && d.success && d.data && d.data.raw) resolve(d.data.raw);
else reject(new Error((d && d.error) || 'empty whois response'));
},
onerror: reject,
ontimeout: function () { reject(new Error('timeout')); }
});
});
return CACHE[key];
}
// 解析 whois 原始文本,提取注册商 / 注册时间 / 到期时间(日期取前 10 位)
function parseWhoisRaw(raw) {
var text = raw || '';
var out = { registrar: '', registered: '', expires: '' };
var m;
m = text.match(/^registrar:\s*(.+)$/im);
if (m) out.registrar = m[1].trim();
m = text.match(/^(?:creation\s*date|registry\s*creation\s*date):\s*(\S+)/im);
if (m) out.registered = String(m[1]).slice(0, 10);
m = text.match(/^(?:registry\s*expiry\s*date|expiry\s*date):\s*(\S+)/im);
if (m) out.expires = String(m[1]).slice(0, 10);
return out;
}
// state: 'free' | 'registered'
function buildRows(domain, state, registrar, registered, expires) {
var stateText = state === 'free' ? '未注册(可注册)' : '已注册';
var stateCls = state === 'free' ? 'nlc-state nlc-state-free' : 'nlc-state nlc-state-reg';
var rows = [
['域名状态', '<span class="' + stateCls + '">' + stateText + '</span>'],
['注册商', esc(registrar || '—')],
['注册时间', esc(registered || '—')],
['到期时间', esc(expires || '—')]
];
var html = '<div class="nlc-row"><span class="nlc-label">域名</span><span class="nlc-value nlc-domain">' + esc(domain) + '</span></div>';
rows.forEach(function (r) {
html += '<div class="nlc-row"><span class="nlc-label">' + esc(r[0]) + '</span><span class="nlc-value">' + r[1] + '</span></div>';
});
return html;
}
function renderBody(body, domain, data) {
var p = (data && data.parsed) || {};
var state = data && data.type === 0 ? 'free' : 'registered';
function d10(v) { return v ? String(v).slice(0, 10) : '—'; }
body.innerHTML = buildRows(
domain,
state,
(p.registrar && p.registrar.name) || '',
d10(p.registered),
d10(p.expires)
);
}
/* ---------------- 正文扫描 ---------------- */
var DOMAIN_RE = /(^|[^a-z0-9.-])((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63})(?=$|[^a-z0-9_.-])/gi;
// 链接:MiddleFun 已把正文里的 URL autolink 成分站外链(target=_blank),
// 直接在 <a> 之后插入放大镜,避免破坏其 inline-flex 布局与尾部的 external-link 图标
function handleAnchor(a) {
if (a.dataset.nlcDone) return;
var href = a.getAttribute('href') || '';
if (!/^https?:\/\//i.test(href)) return;
var host = '';
try { host = new URL(href, location.href).hostname.toLowerCase(); } catch (e) { return; }
if (SKIP_HOSTS[host]) return;
var dom = normalizeDomain(host);
if (!isPlainDomain(dom)) return;
a.dataset.nlcDone = '1';
var mag = makeMagnifier(dom);
a.parentNode.insertBefore(mag, a.nextSibling);
}
function handleLinks(scope) {
var anchors = scope.querySelectorAll('a[href]');
for (var i = 0; i < anchors.length; i++) {
var a = anchors[i];
if (a.dataset.nlcDone) continue;
if (a.classList && a.classList.contains('nlc-mag')) continue;
if (a.closest && a.closest('.nlc-pop')) continue;
handleAnchor(a);
}
}
// 文本节点:匹配裸域名,拆分为 [前文][域名span][放大镜][后文]
function handleTextNode(node) {
// 双保险:脚本自己插入的域名 span / 放大镜内部不再处理,避免重复扫描时叠加
if (node.parentNode && node.parentNode.closest &&
node.parentNode.closest('.nlc-dom,.nlc-mag,.nlc-pop')) return;
var text = node.nodeValue;
if (!text || !/\./.test(text)) return;
DOMAIN_RE.lastIndex = 0;
var m;
var hits = [];
while ((m = DOMAIN_RE.exec(text))) {
var dom = normalizeDomain(m[2]);
if (!isPlainDomain(dom) || SKIP_HOSTS[dom]) continue;
hits.push({ dom: dom, start: m.index + m[0].indexOf(m[2]), len: m[2].length });
}
if (!hits.length) return;
var frag = document.createDocumentFragment();
var pos = 0;
hits.forEach(function (h) {
if (h.start < pos) return; // 防重叠
frag.appendChild(document.createTextNode(text.slice(pos, h.start)));
var span = document.createElement('span');
span.className = 'nlc-dom';
span.setAttribute('data-nlc-domain', h.dom);
span.textContent = text.substr(h.start, h.len);
frag.appendChild(span);
frag.appendChild(makeMagnifier(h.dom));
pos = h.start + h.len;
});
frag.appendChild(document.createTextNode(text.slice(pos)));
node.parentNode.replaceChild(frag, node);
}
// 文本兜底扫描:遍历区域内文本节点(跳过 a / pre / code / svg / 脚本自身节点)
function scanTextTree(root) {
var walker = document.createTreeWalker(
root,
NodeFilter.SHOW_TEXT,
{
acceptNode: function (n) {
if (n.parentNode && n.parentNode.closest && n.parentNode.closest('pre,code,kbd,samp,textarea,svg,.nlc-pop,.nlc-mag,.nlc-dom')) {
return NodeFilter.FILTER_REJECT;
}
var p = n.parentNode;
while (p && p !== root) {
if (p.tagName === 'A' || p.tagName === 'PRE' || p.tagName === 'CODE' ||
p.tagName === 'SCRIPT' || p.tagName === 'STYLE' || p.tagName === 'TEXTAREA' ||
(p.classList && (p.classList.contains('nlc-mag') || p.classList.contains('nlc-dom')))) {
return NodeFilter.FILTER_REJECT;
}
p = p.parentNode;
}
return NodeFilter.FILTER_ACCEPT;
}
}
);
var nodes = [];
while (walker.nextNode()) nodes.push(walker.currentNode);
for (var j = 0; j < nodes.length; j++) handleTextNode(nodes[j]);
}
// MiddleFun 正文与回复统一用 div.prose.prose-sm(回复在 -mt-3 的包裹层内,类名一致)
function scanProse(scope) {
handleLinks(scope);
scanTextTree(scope);
}
function scanAll() {
var list = document.querySelectorAll('div.prose');
for (var i = 0; i < list.length; i++) {
var c = list[i];
if (c.closest && c.closest('.nlc-pop')) continue;
// 幂等:已处理的链接带 data-nlc-done,已拆分的文本节点无域名残留,重扫不会重复注入
scanProse(c);
}
}
// Next.js App Router:客户端跳转后整棵内容树被替换,回复异步注入
var scanTimer = null;
function scheduleScan() {
if (scanTimer) return;
scanTimer = setTimeout(function () { scanTimer = null; scanAll(); }, 200);
}
/* ---------------- 注入与监听 ---------------- */
// 配色全部走站点自身的 shadcn / Tailwind v4 CSS 变量:
// --popover / --popover-foreground / --border / --muted-foreground / --radius,明暗主题自动跟随
function injectStyles() {
if (document.getElementById('nlc-style')) return;
var css = [
/* 放大镜:14px,与正文 text-sm 基线对齐,默认 muted 灰,hover 站点主色绿 */
'.nlc-mag{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;user-select:none;-webkit-tap-highlight-color:transparent;margin:0 1px 0 3px;vertical-align:-3px;color:var(--muted-foreground,#71717b);cursor:pointer;border-radius:calc(var(--radius,.625rem) - 4px);text-decoration:none;opacity:.75;transition:color .12s ease,background-color .12s ease,opacity .12s ease}',
'.nlc-mag:hover{color:var(--nlc-accent,rgb(0,166,62));background:var(--muted,rgba(0,0,0,.05));opacity:1}',
'.nlc-mag svg{display:block}',
'.nlc-mag:focus-visible{outline:2px solid var(--nlc-accent,rgb(0,166,62));outline-offset:1px;opacity:1;color:var(--nlc-accent,rgb(0,166,62))}',
/* 弹窗:站点卡片语言——popover 底色、1px border、--radius 圆角、shadow-lg */
'.nlc-pop{position:fixed;z-index:2147483646;width:296px;max-width:calc(100vw - 24px);background:var(--popover,#fff);color:var(--popover-foreground,#09090b);border:1px solid var(--border,#e4e4e7);border-radius:var(--radius,.625rem);box-shadow:0 10px 15px -3px rgba(0,0,0,.10),0 4px 6px -4px rgba(0,0,0,.10);font-family:var(--font-sans,Geist,ui-sans-serif,system-ui,"PingFang SC","Microsoft YaHei",sans-serif);font-size:13px;line-height:1.55;overflow:hidden;--nlc-accent:rgb(0,166,62);--nlc-muted-bg:color-mix(in oklab,var(--muted,#f4f4f5) 70%,transparent)}',
/* 头部:与站内 sticky header 一致的底边分隔,比正文更轻的底色 */
'.nlc-pop-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 12px;border-bottom:1px solid var(--border,#e4e4e7)}',
'.nlc-pop-title{display:inline-flex;align-items:center;gap:6px;min-width:0;font-size:13.5px;font-weight:600;letter-spacing:-.01em;word-break:break-all}',
'.nlc-pop-tip{display:inline-flex;color:var(--muted-foreground,#71717b);flex:0 0 auto}',
'.nlc-pop-close{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:24px;height:24px;padding:0!important;margin:0!important;border:0!important;border-radius:calc(var(--radius,.625rem) - 5px);background:transparent;color:var(--muted-foreground,#71717b);cursor:pointer;line-height:1!important;transition:color .12s,background-color .12s}',
'.nlc-pop-close:hover{color:var(--popover-foreground,#09090b);background:var(--muted,#f4f4f5)}',
'.nlc-pop-body{padding:4px 12px}',
/* 数据行:Tailwind divide-y 风格,最后一行不留分隔线 */
'.nlc-row{display:flex;align-items:baseline;gap:10px;padding:7px 0;border-bottom:1px solid var(--border,#e4e4e7)}',
'.nlc-row:last-child{border-bottom:0}',
'.nlc-label{flex:0 0 60px;font-size:12px;color:var(--muted-foreground,#71717b);white-space:nowrap}',
'.nlc-value{min-width:0;font-size:12.5px;color:var(--popover-foreground,#09090b);word-break:break-all}',
'.nlc-domain{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}',
'.nlc-state-free{color:var(--nlc-accent,rgb(0,166,62));font-weight:600}',
'.nlc-state-reg{color:var(--popover-foreground,#09090b);font-weight:500}',
'.nlc-loading{display:flex;align-items:center;justify-content:center;gap:8px;padding:14px 0;color:var(--muted-foreground,#71717b);font-size:12.5px}',
'.nlc-spinner{display:inline-block;width:13px;height:13px;flex:0 0 auto;border:2px solid var(--border,#e4e4e7);border-top-color:var(--nlc-accent,rgb(0,166,62));border-radius:50%;animation:nlc-spin .75s linear infinite!important}',
'@keyframes nlc-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}',
'.nlc-err{padding:14px 0;color:var(--destructive,#e11d48);text-align:center;font-size:12.5px}',
/* 底栏:muted 底色 + 主色链接,呼应站内外链的绿色下划线风格 */
'.nlc-pop-foot{padding:8px 12px;border-top:1px solid var(--border,#e4e4e7);background:color-mix(in oklab,var(--muted,#f4f4f5) 55%,transparent);display:flex;align-items:center;justify-content:flex-start}',
'.nlc-pop-more{display:inline-flex;align-items:center;gap:3px;color:var(--nlc-accent,rgb(0,166,62));font-size:12.5px;text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}',
'.nlc-pop-more:hover{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px}',
'.nlc-pop-more svg{flex:0 0 auto;opacity:.85}',
/* 暗色:shadcn 变量基准色为 zinc,主色在暗色下提亮为 green-400 */
'html.dark .nlc-pop,html.dark .nlc-mag,html[data-theme="dark"] .nlc-pop,html[data-theme="dark"] .nlc-mag{--nlc-accent:rgb(74,222,128)}',
'html.dark .nlc-mag:hover,html[data-theme="dark"] .nlc-mag:hover{background:rgba(255,255,255,.08)}',
'html.dark .nlc-pop{box-shadow:0 10px 15px -3px rgba(0,0,0,.5),0 4px 6px -4px rgba(0,0,0,.4)}'
].join('');
var style = document.createElement('style');
style.id = 'nlc-style';
style.textContent = css;
(document.head || document.documentElement).appendChild(style);
}
function init() {
injectStyles();
scanAll();
// Next.js 客户端路由:帖子正文、回复、"查看更多回复"均为异步渲染,持续监听
var observer = new MutationObserver(scheduleScan);
observer.observe(document.body, { childList: true, subtree: true });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
暂无回复,期待你的想法