Files
pear-admin-flask/templates/public/base.html
T
bwadmin e10ef5f243 feat(public): 主题切换改为下拉菜单 + 新增4个主题 + 优化搜索与抽屉交互
- 主题切换由循环切换改为弹出下拉菜单,显示全部可用主题
- 新增紫韵/深海/青空/玫红 4 个主题,共 8 个主题 + 跟随系统
- 清理顶部 .actions 菜单栏与旧主题下拉的残余死 CSS/JS
- 搜索无结果时显示空态提示
- 移动端抽屉打开时锁定背景滚动
- 搜索输入防抖 120ms
- 超宽屏下卡片描述放宽到 2 行,搜索框同步放大
- 修复抽屉主题按钮缺少 JS 绑定的 bug
- .card .url 对比度由 --text-soft 改为 --text-muted
2026-09-07 16:50:19 +08:00

1655 lines
70 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="theme-color" content="#16baaa">
<meta name="format-detection" content="telephone=no">
<title>{% block title %}{{ site_name|default('旺珂 · 导航') }}{% endblock %}</title>
<meta name="description" content="旺珂 公开导航聚合页:精选开发工具、AI、设计资源、效率工具等优质站点。">
<meta property="og:title" content="{{ site_name|default('旺珂 · 导航') }}">
<meta property="og:description" content="精选优质站点,按分类聚合展示。">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/layui@2.9.7/dist/css/layui.css">
{# 首屏防闪烁:根据 localStorage / 系统偏好立即设置 data-theme #}
<script>
(function(){
try {
var saved = localStorage.getItem('bw-theme');
// 有效值:'default'、'sunset'、'forest'、'dark'、'auto'
if (saved === 'auto') {
var isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'default');
} else if (saved === 'default' || saved === 'sunset' || saved === 'forest' || saved === 'dark') {
document.documentElement.setAttribute('data-theme', saved);
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
}
} catch (e) { /* localStorage 不可用时跳过 */ }
})();
</script>
<style>
/* ===== 主题变量 =====
* 默认主题(清新蓝绿):无 data-theme 属性 / data-theme="default"
* 4 种可切换主题:
* default 蓝绿渐变(默认)
* sunset 暖橙 + 玫红
* forest 墨绿 + 青绿
* dark 深色背景
*/
:root, [data-theme="default"] {
--brand: #16baaa;
--brand-2: #2e8de8;
--bg: #f5f7fa;
--card-bg: #ffffff;
--text: #2c3e50;
--text-muted: #6c7a89;
--text-soft: #b0bac3;
--border: #e8eef0;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(0,0,0,0.06);
--shadow-hover: 0 8px 24px rgba(22, 186, 170, 0.18);
}
/* 暮色橙(暖色系) */
[data-theme="sunset"] {
--brand: #ff7043;
--brand-2: #e91e63;
--bg: #fff8f3;
--card-bg: #ffffff;
--text: #3e2723;
--text-muted: #8d6e63;
--text-soft: #bcaaa4;
--border: #ffe0d0;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(255,112,67,0.10);
--shadow-hover: 0 8px 24px rgba(255,112,67,0.25);
}
/* 森林绿(自然系) */
[data-theme="forest"] {
--brand: #2e7d32;
--brand-2: #00897b;
--bg: #f3f8f4;
--card-bg: #ffffff;
--text: #1b3a1f;
--text-muted: #5a7260;
--text-soft: #9bb3a0;
--border: #d8e6da;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(46,125,50,0.08);
--shadow-hover: 0 8px 24px rgba(46,125,50,0.22);
}
/* 暗色(深色背景) */
[data-theme="dark"] {
--brand: #16baaa;
--brand-2: #2e8de8;
--bg: #0f1419;
--card-bg: #1a1f29;
--text: #e6edf3;
--text-muted: #8b949e;
--text-soft: #6e7681;
--border: #2d333b;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(0,0,0,0.4);
--shadow-hover: 0 8px 24px rgba(22, 186, 170, 0.25);
}
/* 紫韵(优雅紫色) */
[data-theme="lavender"] {
--brand: #8b5cf6;
--brand-2: #ec4899;
--bg: #faf5ff;
--card-bg: #ffffff;
--text: #2e1065;
--text-muted: #6d28d9;
--text-soft: #a78bfa;
--border: #e9d5ff;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(139,92,246,0.10);
--shadow-hover: 0 8px 24px rgba(139,92,246,0.25);
}
/* 深海(深蓝 navy */
[data-theme="ocean"] {
--brand: #0ea5e9;
--brand-2: #06b6d4;
--bg: #f0f9ff;
--card-bg: #ffffff;
--text: #0c4a6e;
--text-muted: #0369a1;
--text-soft: #7dd3fc;
--border: #bae6fd;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(14,165,233,0.10);
--shadow-hover: 0 8px 24px rgba(14,165,233,0.28);
}
/* 青空(明亮天蓝) */
[data-theme="sky"] {
--brand: #38bdf8;
--brand-2: #3b82f6;
--bg: #f0f9ff;
--card-bg: #ffffff;
--text: #0c4a6e;
--text-muted: #0284c7;
--text-soft: #7dd3fc;
--border: #e0f2fe;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(59,130,246,0.10);
--shadow-hover: 0 8px 24px rgba(59,130,246,0.28);
}
/* 玫红(热情玫红) */
[data-theme="rose"] {
--brand: #f43f5e;
--brand-2: #ec4899;
--bg: #fff1f2;
--card-bg: #ffffff;
--text: #4a040c;
--text-muted: #be123c;
--text-soft: #fda4af;
--border: #fecdd3;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(244,63,94,0.10);
--shadow-hover: 0 8px 24px rgba(244,63,94,0.28);
}
/* 系统暗色兜底:未设置 data-theme 时跟随系统 */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--brand: #16baaa;
--brand-2: #2e8de8;
--bg: #0f1419;
--card-bg: #1a1f29;
--text: #e6edf3;
--text-muted: #8b949e;
--text-soft: #6e7681;
--border: #2d333b;
--shadow: 0 2px 12px rgba(0,0,0,0.4);
--shadow-hover: 0 8px 24px rgba(22, 186, 170, 0.25);
}
}
/* ===== 布局变量:容器宽度 / 侧栏宽度 / 卡片最小宽度 =====
* 默认 1200px 居中;宽屏按 1440 / 1700 / 2000 / 2560 四档逐级放大,
* 避免 2K、4K 下内容缩在中间一小条。
*/
:root {
--container-max: 1200px;
--side-w: 220px;
--card-min: 220px;
--layout-gap: 24px;
}
@media (min-width: 1440px) {
:root { --container-max: 1360px; --side-w: 232px; --card-min: 196px; --layout-gap: 28px; }
}
@media (min-width: 1700px) {
:root { --container-max: 1600px; --side-w: 244px; --card-min: 250px; --layout-gap: 32px; }
}
@media (min-width: 2000px) {
:root { --container-max: 1880px; --side-w: 258px; --card-min: 200px; --layout-gap: 36px; }
}
@media (min-width: 2560px) {
:root { --container-max: 2240px; --side-w: 276px; --card-min: 210px; --layout-gap: 44px; }
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
font-size: 15px;
line-height: 1.5;
transition: background-color 0.2s, color 0.2s;
}
/* ===== Topbar ===== */
.topbar {
background: linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 100%);
color: var(--topbar-text);
padding: 56px 0 40px;
position: relative;
}
.topbar-inner { max-width: 100%; margin: 0 auto; padding: 0 24px; }
.topbar h1 { font-size: 36px; margin: 0 0 8px; font-weight: 600; line-height: 1.2; }
.topbar .sub { font-size: 15px; opacity: 0.92; }
/* 宽屏:标题与内边距同步放大,避免头重脚轻 */
@media (min-width: 1700px) {
.topbar { padding: 68px 0 52px; }
.topbar h1 { font-size: 44px; }
.topbar .sub { font-size: 17px; }
.site-search { max-width: 760px; }
/* 卡片描述放宽到 2 行,提升信息密度 */
.card .desc { -webkit-line-clamp: 2; min-height: 40px; }
}
@media (min-width: 2560px) {
.topbar { padding: 84px 0 64px; }
.topbar h1 { font-size: 52px; }
.site-search { max-width: 880px; }
}
/* 手机端分类入口按钮:默认桌面隐藏 */
.cat-trigger-mobile {
display: none;
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.35);
color: var(--topbar-text);
border-radius: 10px;
width: 44px; height: 44px;
align-items: center; justify-content: center;
font-size: 20px;
cursor: pointer;
flex-shrink: 0;
}
.cat-trigger-mobile:active { background: rgba(255,255,255,0.32); }
/* ===== 主题下拉菜单 ===== */
.theme-picker-trigger { position: relative; }
.theme-menu {
display: none;
position: absolute;
top: calc(100% + 6px);
left: 0;
min-width: 150px;
margin: 0; padding: 4px;
list-style: none;
background: var(--card-bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: 10px;
box-shadow: var(--shadow-hover);
z-index: 50;
}
.theme-menu.open { display: block; }
.theme-menu li {
display: block;
padding: 8px 12px;
font-size: 14px;
border-radius: 6px;
cursor: pointer;
transition: background 0.12s;
color: var(--text);
}
.theme-menu li:hover { background: rgba(22,186,170,0.10); }
.theme-menu li.active { background: rgba(22,186,170,0.18); font-weight: 600; }
/* ===== 顶部全局搜索 ===== */
.site-search {
position: relative;
max-width: 560px;
margin-top: 22px;
display: flex;
align-items: center;
background: rgba(255,255,255,0.95);
border-radius: 999px;
padding: 4px 6px 4px 18px;
box-shadow: 0 6px 20px rgba(0,0,0,0.12);
transition: box-shadow 0.2s;
}
.site-search:focus-within { box-shadow: 0 8px 28px rgba(0,0,0,0.18); }
.site-search i {
color: var(--text-muted);
font-size: 18px;
margin-right: 10px;
}
.site-search input[type="search"] {
flex: 1;
border: none;
background: transparent;
font-size: 15px;
color: var(--text);
outline: none;
padding: 10px 0;
min-width: 0;
}
.site-search input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
.site-search input[type="search"]::placeholder { color: #9aa5b1; }
.site-search .search-btn {
padding: 8px 22px;
background: linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 100%);
color: #fff;
border: none;
border-radius: 999px;
font-size: 14px;
cursor: pointer;
transition: opacity 0.2s;
}
.site-search .search-btn:hover { opacity: 0.9; }
.container { max-width: 100%; margin: 0 auto; padding: 0 24px; }
/* ===== 分类目录条(横向滚动) ===== */
.cat-nav {
background: var(--card-bg);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 10;
backdrop-filter: saturate(180%) blur(8px);
-webkit-backdrop-filter: saturate(180%) blur(8px);
}
.cat-nav-inner {
max-width: 100%;
margin: 0 auto;
padding: 8px 24px;
display: flex;
gap: 6px;
overflow-x: auto;
scrollbar-width: thin;
scroll-behavior: smooth;
}
.cat-nav-inner::-webkit-scrollbar { height: 4px; }
.cat-nav-inner::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
.cat-nav a {
flex-shrink: 0;
padding: 6px 14px;
border-radius: 999px;
font-size: 13px;
color: var(--text-muted);
text-decoration: none;
transition: all 0.15s;
white-space: nowrap;
}
.cat-nav a:hover { background: rgba(22,186,170,0.1); color: var(--brand); }
.cat-nav a.active { background: var(--brand); color: #fff; }
/* ===== 移动端分类 FAB + 抽屉 ===== */
.cat-fab {
display: none; /* 桌面隐藏 */
position: fixed;
right: 14px;
bottom: 76px; /* 留出回到顶部按钮的位置 */
padding: 10px 14px;
background: var(--brand);
color: #fff;
border: none;
border-radius: 999px;
font-size: 14px;
font-weight: 600;
box-shadow: 0 6px 16px rgba(22,186,170,0.35);
z-index: 25;
cursor: pointer;
white-space: nowrap;
}
.cat-fab:active { transform: scale(0.96); }
.cat-drawer {
display: none;
position: fixed;
inset: 0;
z-index: 30;
background: rgba(0,0,0,0.5);
}
.cat-drawer.open { display: block; }
/* 抽屉打开时锁定背景滚动 */
body.drawer-open { overflow: hidden; }
/* 搜索无结果空态 */
.search-empty {
text-align: center;
color: var(--text-muted);
padding: 64px 16px;
font-size: 15px;
}
.search-empty .ico { font-size: 40px; display: block; margin-bottom: 12px; opacity: 0.6; }
.cat-drawer-panel {
position: absolute;
left: 0; right: 0; bottom: 0;
background: var(--card-bg);
border-radius: 16px 16px 0 0;
padding: 16px 16px 28px;
max-height: 70vh;
overflow-y: auto;
transform: translateY(100%);
transition: transform 0.25s ease;
}
.cat-drawer.open .cat-drawer-panel { transform: translateY(0); }
.cat-drawer-head {
display: flex; align-items: center; justify-content: space-between;
font-size: 15px; font-weight: 600; color: var(--text);
margin-bottom: 14px;
}
.cat-drawer-head .close {
background: none; border: none; font-size: 22px; color: var(--text-muted); cursor: pointer;
}
.cat-drawer-list { list-style: none; padding: 0; margin: 0; }
.cat-drawer-list li a {
display: flex; align-items: center; justify-content: space-between;
padding: 14px 8px;
border-bottom: 1px solid var(--border);
color: var(--text);
text-decoration: none;
font-size: 15px;
}
.cat-drawer-list li:last-child a { border-bottom: none; }
.cat-drawer-list li a:active { background: rgba(22,186,170,0.08); }
.cat-drawer-list li a .arrow { color: var(--text-muted); font-size: 13px; }
/* ===== 布局:左分类侧栏 + 右内容区 ===== */
.layout {
display: flex;
align-items: flex-start;
gap: var(--layout-gap);
}
.content { flex: 1 1 auto; min-width: 0; padding-top: 8px; }
.side-nav {
flex: 0 0 var(--side-w);
width: var(--side-w);
position: sticky;
top: 20px;
max-height: calc(100vh - 40px);
overflow-y: auto;
overscroll-behavior: contain;
padding: 8px 0 20px;
}
.side-nav::-webkit-scrollbar { width: 4px; }
.side-nav::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
.side-block {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: var(--shadow);
padding: 13px 10px;
margin-bottom: 14px;
}
.side-title {
display: flex; align-items: center; gap: 6px;
font-size: 12px; font-weight: 600;
color: var(--text-muted);
letter-spacing: 0.5px;
padding: 0 8px 8px;
margin-bottom: 4px;
border-bottom: 1px solid var(--border);
}
.side-title i { color: var(--brand); font-size: 14px; }
.side-list { list-style: none; margin: 0; padding: 4px 0 0; }
.side-list li a {
display: flex; align-items: center; gap: 9px;
padding: 8px 10px;
border-radius: 8px;
font-size: 14px;
color: var(--text);
text-decoration: none;
transition: background 0.15s, color 0.15s;
}
.side-list li a:hover { background: rgba(22,186,170,0.10); color: var(--brand); }
.side-list li a.active {
background: rgba(22,186,170,0.16);
color: var(--brand);
font-weight: 600;
box-shadow: inset 3px 0 0 var(--brand);
}
.side-list li a .side-ico {
color: var(--brand); font-size: 16px;
width: 18px; text-align: center; flex-shrink: 0;
}
.side-list li a .side-txt {
flex: 1; min-width: 0;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.side-list li a .side-count {
font-size: 11px; color: var(--text-soft); flex-shrink: 0;
}
/* 搜索过滤:分类无命中时整项隐藏 */
.side-list li.hidden-by-search { display: none; }
/* 侧栏特殊项:我常访问(紫)/ 热门(橙) */
.side-list a.side-mine, .side-list a.side-mine .side-ico { color: #8b5cf6; }
.side-list a.side-mine:hover, .side-list a.side-mine.active {
background: rgba(139,92,246,0.14); box-shadow: inset 3px 0 0 #8b5cf6;
}
.side-list a.side-hot, .side-list a.side-hot .side-ico { color: #ff6b35; }
.side-list a.side-hot:hover, .side-list a.side-hot.active {
background: rgba(255,107,53,0.13); box-shadow: inset 3px 0 0 #ff6b35;
}
/* 桌面(≥1025px):用左侧侧栏替代顶部横条 */
@media (min-width: 1025px) {
.cat-nav { display: none; }
}
/* 平板/手机(≤1024px):侧栏收起,回到顶部横条 + 抽屉 */
@media (max-width: 1024px) {
.layout { display: block; }
.side-nav { display: none; }
/* 顶部横条是 sticky 的,锚点跳转要留出它的高度 */
.section { scroll-margin-top: 64px; }
}
/* ===== Section ===== */
.section { margin: 32px 0; scroll-margin-top: 24px; }
.section-header {
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
border-left: 4px solid var(--brand);
padding-left: 12px;
}
.section-header h2 { font-size: 20px; margin: 0; font-weight: 600; }
.section-header .meta { font-size: 13px; color: var(--text-muted); }
/* ===== Grid ===== */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(var(--card-min), 1fr));
gap: 16px;
}
.card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 10px;
padding: 14px 16px;
text-decoration: none;
color: inherit;
box-shadow: var(--shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
display: flex; align-items: center; gap: 12px;
min-width: 0;
}
.card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-hover);
border-color: var(--brand);
}
.card:active { transform: translateY(0); }
/* ===== 热门网址卡片:与普通卡片区分 ===== */
.section-hot { margin-top: 8px; }
.section-hot .section-header h2 i { color: #ff6b35; margin-right: 2px; }
.card-top {
border-color: rgba(22, 186, 170, 0.35);
background: linear-gradient(135deg, var(--card-bg) 0%, rgba(22, 186, 170, 0.05) 100%);
position: relative;
}
.card-top::before {
content: '';
position: absolute; top: 0; left: 0;
width: 3px; height: 100%;
background: var(--brand);
border-radius: 10px 0 0 10px;
}
.card-top:hover {
border-color: var(--brand);
box-shadow: 0 6px 20px rgba(22, 186, 170, 0.18);
}
.cat-nav-inner a.cat-nav-hot {
color: #ff6b35;
font-weight: 600;
}
/* ===== 我常访问(个人榜):紫色系,与热门的橙色区分 ===== */
.section-mine { margin-top: 8px; }
.section-mine .section-header h2 i { color: #8b5cf6; margin-right: 2px; }
.section-mine .section-header .meta a { color: var(--text-muted); text-decoration: underline; }
.section-mine .section-header .meta a:hover { color: #8b5cf6; }
.card-mine {
border-color: rgba(139, 92, 246, 0.35);
background: linear-gradient(135deg, var(--card-bg) 0%, rgba(139, 92, 246, 0.06) 100%);
position: relative;
}
.card-mine::before {
content: '';
position: absolute; top: 0; left: 0;
width: 3px; height: 100%;
background: #8b5cf6;
border-radius: 10px 0 0 10px;
}
.card-mine:hover {
border-color: #8b5cf6;
box-shadow: 0 6px 20px rgba(139, 92, 246, 0.18);
}
.card-mine .visit-count { color: #8b5cf6; }
.cat-nav-inner a.cat-nav-mine { color: #8b5cf6; font-weight: 600; }
.cat-drawer-list a.cat-drawer-mine { color: #8b5cf6; font-weight: 600; }
.card .icon-wrap {
width: 42px; height: 42px;
background: rgba(22, 186, 170, 0.1);
color: var(--brand);
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 20px;
flex-shrink: 0;
}
.card-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.card .title-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.card .title {
font-size: 15px; font-weight: 600; color: var(--text);
flex: 1; min-width: 0;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.card .ext-tag {
flex-shrink: 0;
font-size: 12px; color: var(--brand-2);
background: rgba(46, 141, 232, 0.08);
padding: 1px 6px;
border-radius: 4px;
}
.card .desc {
font-size: 13px; color: var(--text-muted);
line-height: 1.5;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
min-height: 20px;
}
.card .url {
font-size: 12px; color: var(--text-muted);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
direction: rtl;
text-align: left;
}
.card .visit-count {
margin-top: auto;
font-size: 11px;
color: var(--text-muted);
text-align: right;
letter-spacing: 0.3px;
opacity: 0.75;
padding-top: 2px;
}
.card-arrow {
flex-shrink: 0;
color: var(--text-soft);
font-size: 14px;
display: flex; align-items: center;
}
.empty {
text-align: center; padding: 48px 0; color: var(--text-muted);
}
/* ===== 关于页样式 ===== */
.card-section {
background: var(--card-bg);
border-radius: 12px;
padding: 28px 32px;
box-shadow: var(--shadow);
margin-bottom: 20px;
}
.about-header h1 {
margin: 0 0 8px;
font-size: 24px;
color: var(--text);
}
.icp-line {
font-size: 13px;
color: var(--text-muted);
}
.icp-line a {
color: var(--text-muted);
text-decoration: none;
}
.icp-line a:hover { color: var(--brand); }
.beian-shield { margin-right: 4px; }
.about-content { line-height: 1.8; color: var(--text); margin-top: 12px; }
.about-content h1,
.about-content h2,
.about-content h3 {
margin: 18px 0 10px;
color: var(--text);
font-weight: 600;
}
.about-content h1 { font-size: 22px; }
.about-content h2 { font-size: 19px; }
.about-content h3 { font-size: 16px; }
.about-content p { margin: 8px 0; }
.about-content code {
background: rgba(22,186,170,0.08);
padding: 1px 6px;
border-radius: 3px;
font-family: Consolas, monospace;
font-size: 13px;
color: var(--brand);
}
.about-content pre {
background: rgba(0,0,0,0.04);
padding: 12px 16px;
border-radius: 6px;
overflow-x: auto;
}
[data-theme="dark"] .about-content pre { background: rgba(255,255,255,0.06); }
.about-content blockquote {
border-left: 4px solid var(--brand);
margin: 12px 0;
padding: 4px 16px;
background: rgba(22,186,170,0.06);
color: var(--text-muted);
}
.about-content ul, .about-content ol { padding-left: 24px; }
.about-content table {
border-collapse: collapse;
width: 100%;
margin: 12px 0;
}
.about-content table th,
.about-content table td {
border: 1px solid var(--border);
padding: 8px 12px;
}
.contact-grid h2,
.donate-block h2 {
font-size: 17px;
margin: 16px 0 12px;
color: var(--text);
border-left: 3px solid var(--brand);
padding-left: 10px;
}
.contact-grid ul {
list-style: none;
padding: 0;
margin: 0;
}
.contact-grid ul li {
padding: 6px 0;
font-size: 14px;
color: var(--text);
border-bottom: 1px dashed var(--border);
}
.contact-grid ul li:last-child { border-bottom: none; }
.contact-grid ul li b { color: var(--brand); margin-right: 6px; }
.donate-block { text-align: center; padding-bottom: 12px; }
.donate-block .donate-img {
max-width: 220px;
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px;
background: #fff;
}
.donate-block .layui-btn-warm { background: #ff7043; }
.about-footer-meta {
margin-top: 20px;
padding-top: 12px;
border-top: 1px solid var(--border);
font-size: 12px;
color: var(--text-soft);
text-align: center;
}
footer.site-footer {
margin-top: 48px; padding: 24px 0;
text-align: center; color: var(--text-muted); font-size: 13px;
border-top: 1px solid var(--border);
}
footer.site-footer a { color: var(--brand); text-decoration: none; }
footer.site-footer .footer-privacy { font-size: 11px; color: var(--text-muted); margin-top: 4px; opacity: 0.8; }
/* ===== 回到顶部 ===== */
.to-top {
position: fixed;
right: 20px; bottom: 20px;
width: 42px; height: 42px;
background: var(--brand);
color: #fff;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
cursor: pointer;
box-shadow: var(--shadow-hover);
opacity: 0;
transform: translateY(20px);
transition: all 0.2s;
pointer-events: none;
z-index: 20;
border: none;
font-size: 18px;
}
.to-top.visible { opacity: 1; transform: translateY(0); pointer-events: auto; }
/* ===== 响应式:平板 ===== */
@media (max-width: 900px) {
.grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.topbar h1 { font-size: 30px; }
.site-search { max-width: 100%; }
}
/* ===== 响应式:手机(OneNav 风格:顶部图标入口 + 左侧分类抽屉 + 分组列表) ===== */
@media (max-width: 600px) {
body { font-size: 14px; }
.container { padding: 0 14px; }
.topbar { padding: 12px 0; }
.topbar-inner {
padding: 0 14px;
display: flex;
align-items: center;
gap: 10px;
}
.topbar-brand { display: none; } /* 手机顶部只保留分类按钮+搜索 */
.cat-trigger-mobile { display: inline-flex; }
/* 顶部搜索框更紧凑,占满剩余空间 */
.site-search {
flex: 1;
margin-top: 0;
max-width: none;
padding: 3px 4px 3px 12px;
}
.site-search i { font-size: 16px; margin-right: 8px; }
.site-search input[type="search"] { font-size: 14px; padding: 8px 0; }
.site-search .search-btn { padding: 7px 14px; font-size: 13px; }
/* 手机端:横向分类 Tab 隐藏(用抽屉替代) */
.cat-nav { display: none; }
.section { margin: 18px 0; scroll-margin-top: 16px; }
.section-header { padding-left: 10px; margin-bottom: 12px; }
.section-header h2 { font-size: 16px; }
.section-header .meta { font-size: 12px; }
/* 手机端:卡片改成两列网格 */
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.card {
padding: 11px 12px;
border-radius: 10px;
gap: 9px;
align-items: flex-start;
}
.card .icon-wrap { width: 32px; height: 32px; font-size: 16px; border-radius: 8px; flex-shrink: 0; }
.card-body { gap: 2px; min-width: 0; }
.card .title { font-size: 13px; line-height: 1.3; }
.card .desc {
font-size: 11px;
min-height: 0;
-webkit-line-clamp: 2;
line-height: 1.35;
}
.card .url { font-size: 10px; }
.card .visit-count { font-size: 10px; padding-top: 1px; }
.card-arrow { display: none; } /* 二列空间紧,隐藏右箭头 */
.ext-tag { display: none; } /* 隐藏外链标记,让标题空间更大 */
/* 手机端:分类抽屉替代 FAB */
.cat-fab { display: none !important; }
.to-top { right: 12px; bottom: 12px; width: 38px; height: 38px; }
/* 分类抽屉样式 */
.cat-drawer-panel {
left: 0; right: auto; top: 0; bottom: 0;
border-radius: 0 16px 16px 0;
max-height: 100vh;
max-width: 280px;
transform: translateX(-100%);
padding: 20px 16px 28px;
}
.cat-drawer.open .cat-drawer-panel { transform: translateX(0); }
.cat-drawer-list li a {
justify-content: flex-start !important;
gap: 12px;
font-size: 15px;
padding: 14px 10px;
}
.cat-drawer-list li a .cat-icon {
display: inline-block;
color: var(--brand);
font-size: 18px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
.cat-drawer-list li a .arrow { margin-left: auto; }
/* 抽屉分区 */
.cat-drawer-section { margin-bottom: 18px; }
.cat-drawer-title {
display: flex; align-items: center; gap: 6px;
font-size: 12px; font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 0 4px 8px;
border-bottom: 1px solid var(--border);
margin-bottom: 6px;
}
.cat-drawer-title i { color: var(--brand); font-size: 14px; }
}
</style>
{% block extra_head %}{% endblock %}
</head>
<body>
<header class="topbar">
<div class="container topbar-inner">
{# 手机端分类入口按钮(OneNav 风格) #}
<button type="button" class="cat-trigger-mobile" id="cat-trigger-mobile" aria-label="打开分类" title="分类">
<i class="layui-icon layui-icon-spread-left"></i>
</button>
<div class="topbar-brand">
<h1>{{ site_name|default('旺珂 · 导航') }}</h1>
<div class="sub">{% block subtitle %}精选优质站点,按分类聚合展示 · 共 {{ total|default(0) }} 个链接{% endblock %}</div>
</div>
{# 全局搜索框(OneNav 风格) #}
<form class="site-search" id="site-search" action="{{ url_for('public_nav.index') }}" method="get" role="search">
<i class="layui-icon layui-icon-search"></i>
<input type="search" name="q" id="search-input" placeholder="输入书签关键词进行搜索" autocomplete="off" aria-label="搜索书签">
<button type="submit" class="search-btn" aria-label="搜索">搜索</button>
</form>
</div>
</header>
{% block cat_nav %}{% endblock %}
{# 首页时锚点直接指向本页 section;其他页面(关于/友链/分类详情)跳回首页对应位置 #}
{% set is_home = request.endpoint in ['public_nav.index', 'index.index'] %}
{% set home_url = url_for('public_nav.index') %}
{% set anchor_base = '#' if is_home else home_url ~ '#' %}
<div class="container layout">
{# ===== 左侧分类侧栏:桌面常驻(≥1025px);平板/手机隐藏,改用顶部横条 + 抽屉 ===== #}
<aside class="side-nav" id="side-nav" aria-label="分类导航"{% if is_home %} data-home="1"{% endif %}>
{% if my_navs is defined and my_navs %}
<div class="side-block side-mine-block">
<div class="side-title"><i class="layui-icon layui-icon-star-fill"></i> 我的</div>
<ul class="side-list">
<li><a href="{{ anchor_base }}cat-mine" class="side-mine" data-side-anchor="cat-mine">
<i class="layui-icon side-ico layui-icon-star-fill"></i>
<span class="side-txt">我常访问</span>
</a></li>
</ul>
</div>
{% endif %}
{% if top_navs is defined and top_navs %}
<div class="side-block side-hot-block">
<div class="side-title"><i class="layui-icon layui-icon-fire"></i> 热门</div>
<ul class="side-list">
<li><a href="{{ anchor_base }}cat-top" class="side-hot" data-side-anchor="cat-top">
<i class="layui-icon side-ico layui-icon-fire"></i>
<span class="side-txt">热门网址</span>
</a></li>
</ul>
</div>
{% endif %}
<div class="side-block" id="side-cat-block">
<div class="side-title"><i class="layui-icon layui-icon-list"></i> 书签分类</div>
<ul class="side-list" id="side-cat-list">
{% for c in nav_categories() %}
<li>
<a href="{{ anchor_base }}{{ c.anchor }}" data-side-anchor="{{ c.anchor }}" title="{{ c.name }}"{% if category is defined and category == c.name %} class="active"{% endif %}>
<i class="layui-icon side-ico {{ c.icon }}"></i>
<span class="side-txt">{{ c.name }}</span>
<span class="side-count"></span>
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="side-block">
<div class="side-title"><i class="layui-icon layui-icon-link"></i> 站点页面</div>
<ul class="side-list">
<li><a href="{{ home_url }}">
<i class="layui-icon side-ico layui-icon-home"></i>
<span class="side-txt">首页</span></a></li>
<li><a href="{{ url_for('public_about.about') }}">
<i class="layui-icon side-ico layui-icon-about"></i>
<span class="side-txt">关于本站</span></a></li>
<li><a href="{{ url_for('public_friend.friend_index') }}">
<i class="layui-icon side-ico layui-icon-website"></i>
<span class="side-txt">友情链接</span></a></li>
<li><a href="/system/passport/login">
<i class="layui-icon side-ico layui-icon-password"></i>
<span class="side-txt">登录后台</span></a></li>
<li class="theme-picker-trigger">
<a href="javascript:void(0)" id="side-theme-toggle">
<i class="layui-icon side-ico layui-icon-light"></i>
<span class="side-txt">切换主题</span></a>
<ul class="theme-menu" id="side-theme-menu">
<li data-theme="default">蓝绿</li>
<li data-theme="sunset">暮色</li>
<li data-theme="forest">森林</li>
<li data-theme="dark">暗色</li>
<li data-theme="lavender">紫韵</li>
<li data-theme="ocean">深海</li>
<li data-theme="sky">青空</li>
<li data-theme="rose">玫红</li>
<li data-theme="auto">跟随系统</li>
</ul>
</li>
</ul>
</div>
</aside>
<main class="content">
<div id="search-empty" class="search-empty" hidden></div>
{% block content %}{% endblock %}
</main>
</div>
<footer class="site-footer">
<div>© 2026 旺珂 · 基于 <a href="https://gitea.bwhome.top/bwadmin/pear-admin-flask">Pear Admin Flask</a> 构建</div>
<div class="footer-privacy">本站使用匿名统计:会生成一个随机匿名 ID 用于记住你的常用网址,不关联任何个人信息,可在「我常访问」里一键清除。</div>
</footer>
<button class="to-top" id="to-top" aria-label="回到顶部" title="回到顶部"></button>
{# 移动端分类 FAB + 抽屉(桌面被 CSS 隐藏,无需各页面写) #}
<button type="button" class="cat-fab" id="cat-fab" aria-haspopup="dialog" aria-controls="cat-drawer">
📂 分类 (<span id="cat-fab-count">0</span>)
</button>
<div class="cat-drawer" id="cat-drawer" role="dialog" aria-modal="true" aria-label="导航抽屉">
<div class="cat-drawer-panel">
<div class="cat-drawer-head">
<span>导航菜单</span>
<button type="button" class="close" id="cat-drawer-close" aria-label="关闭">×</button>
</div>
{# 我常访问入口(仅首页,且必须传了 my_navs 才渲染)#}
{% if my_navs is defined and my_navs %}
<div class="cat-drawer-section">
<div class="cat-drawer-title"><i class="layui-icon layui-icon-star-fill"></i> 我的</div>
<ul class="cat-drawer-list">
<li>
<a href="{{ anchor_base }}cat-mine" class="cat-drawer-mine">
<i class="layui-icon cat-icon layui-icon-star-fill"></i>
<span>我常访问</span>
<span class="arrow"></span>
</a>
</li>
</ul>
</div>
{% endif %}
{# 热门网址入口(仅首页,且首页必须传 top_navs 才渲染)#}
{% if top_navs is defined and top_navs %}
<div class="cat-drawer-section">
<div class="cat-drawer-title"><i class="layui-icon layui-icon-fire"></i> 热门</div>
<ul class="cat-drawer-list">
<li>
<a href="{{ anchor_base }}cat-top" class="cat-drawer-hot">
<i class="layui-icon cat-icon layui-icon-fire"></i>
<span>热门网址</span>
<span class="arrow"></span>
</a>
</li>
</ul>
</div>
{% endif %}
{# 分类区 #}
<div class="cat-drawer-section">
<div class="cat-drawer-title"><i class="layui-icon layui-icon-list"></i> 书签分类</div>
<ul class="cat-drawer-list" id="cat-drawer-list">
{% for c in nav_categories() %}
<li>
<a href="{{ anchor_base }}{{ c.anchor }}" data-side-anchor="{{ c.anchor }}">
<i class="layui-icon cat-icon {{ c.icon }}"></i>
<span>{{ c.name }}</span>
<span class="arrow"></span>
</a>
</li>
{% endfor %}
</ul>
</div>
{# 其他页面入口(关于 / 友链 / 后台 / 主题) #}
<div class="cat-drawer-section">
<div class="cat-drawer-title"><i class="layui-icon layui-icon-link"></i> 站点页面</div>
<ul class="cat-drawer-list cat-drawer-extras">
<li><a href="{{ url_for('public_nav.index') }}">
<i class="layui-icon cat-icon layui-icon-home"></i>
<span>首页</span>
<span class="arrow"></span></a></li>
<li><a href="{{ url_for('public_about.about') }}">
<i class="layui-icon cat-icon layui-icon-about"></i>
<span>关于本站</span>
<span class="arrow"></span></a></li>
<li><a href="{{ url_for('public_friend.friend_index') }}">
<i class="layui-icon cat-icon layui-icon-website"></i>
<span>友情链接</span>
<span class="arrow"></span></a></li>
<li><a href="/system/passport/login">
<i class="layui-icon cat-icon layui-icon-password"></i>
<span>登录后台</span>
<span class="arrow"></span></a></li>
<li class="theme-picker-trigger">
<a href="javascript:void(0)" id="drawer-theme-toggle">
<i class="layui-icon cat-icon layui-icon-light"></i>
<span>切换主题</span>
<span class="arrow"></span></a>
<ul class="theme-menu" id="drawer-theme-menu">
<li data-theme="default">蓝绿</li>
<li data-theme="sunset">暮色</li>
<li data-theme="forest">森林</li>
<li data-theme="dark">暗色</li>
<li data-theme="lavender">紫韵</li>
<li data-theme="ocean">深海</li>
<li data-theme="sky">青空</li>
<li data-theme="rose">玫红</li>
<li data-theme="auto">跟随系统</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
{% block extra_js %}{% endblock %}
<script>
// ===== 主题切换:多主题选择 + localStorage 持久化 + 跟随系统 =====
(function(){
var THEME_KEY = 'bw-theme';
var FIXED_THEMES = ['default', 'sunset', 'forest', 'dark'];
var ALL = FIXED_THEMES.concat(['auto']);
function isSystemDark(){
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
// 把"auto"解析为实际生效的主题
function resolveTheme(name){
if (name === 'auto') return isSystemDark() ? 'dark' : 'default';
return FIXED_THEMES.indexOf(name) !== -1 ? name : 'default';
}
// 应用主题(persist=true 表示记住用户选择)
function applyTheme(name, persist){
if (ALL.indexOf(name) === -1) name = 'default';
var resolved = resolveTheme(name);
document.documentElement.setAttribute('data-theme', resolved);
// 顶部 meta 颜色随主题变化(暗色用深色,其他用对应品牌色)
var themeColor = (resolved === 'dark') ? '#0f1419' :
(resolved === 'lavender') ? '#8b5cf6' :
(resolved === 'ocean') ? '#0ea5e9' :
(resolved === 'sky') ? '#38bdf8' :
(resolved === 'rose') ? '#f43f5e' : '#16baaa';
var meta = document.querySelector('meta[name="theme-color"]');
if (meta) meta.setAttribute('content', themeColor);
// 高亮菜单项
document.querySelectorAll('.theme-menu li').forEach(function(li){
if (li.getAttribute('data-theme') === name) li.classList.add('active');
else li.classList.remove('active');
});
if (persist) localStorage.setItem(THEME_KEY, name);
}
// 加载:localStorage 优先;未保存则跟随系统;若无系统信息则 default
var saved = localStorage.getItem(THEME_KEY);
if (saved && ALL.indexOf(saved) !== -1) {
applyTheme(saved, false);
} else if (isSystemDark()) {
applyTheme('auto', false); // 等价于 'dark',但记录 auto 让用户看到"跟随系统"高亮
localStorage.setItem(THEME_KEY, 'auto'); // 顺手持久化
} else {
applyTheme('default', false);
}
// 顶部按钮:展开/收起菜单
var btn = document.getElementById('theme-toggle');
var menu = document.getElementById('theme-menu');
if (btn && menu) {
btn.addEventListener('click', function(e){
e.stopPropagation();
menu.classList.toggle('open');
});
menu.querySelectorAll('li').forEach(function(li){
li.addEventListener('click', function(e){
e.stopPropagation();
applyTheme(this.getAttribute('data-theme'), true);
menu.classList.remove('open');
});
});
document.addEventListener('click', function(){
menu.classList.remove('open');
});
}
// 跟随系统:监听系统暗色偏好变化,自动重应用
if (window.matchMedia) {
var mq = window.matchMedia('(prefers-color-scheme: dark)');
var listener = function(){
var cur = localStorage.getItem(THEME_KEY) || 'default';
if (cur === 'auto') applyTheme('auto', false);
};
if (mq.addEventListener) mq.addEventListener('change', listener);
else if (mq.addListener) mq.addListener(listener); // 旧 Safari 兼容
}
})();
// 回到顶部
var top = document.getElementById('to-top');
if (top) {
window.addEventListener('scroll', function(){
if (window.scrollY > 400) top.classList.add('visible');
else top.classList.remove('visible');
});
top.addEventListener('click', function(){
window.scrollTo({top: 0, behavior: 'smooth'});
});
}
// 目录高亮当前 section:左侧侧栏(桌面)/ 顶部横条(平板)同步高亮
(function(){
var allLinks = [].slice.call(
document.querySelectorAll('.cat-nav a[href^="#"], .side-nav a[href^="#"]')
);
if (!allLinks.length) return;
var map = {}, sections = [];
allLinks.forEach(function(a){
var id = (a.getAttribute('href') || '').split('#').pop();
var el = id ? document.getElementById(id) : null;
if (!el) return;
if (!map[id]) { map[id] = []; sections.push({id: id, el: el}); }
map[id].push(a);
});
if (!sections.length) return;
function onScroll(){
var y = window.scrollY + 120;
var activeId = sections[0].id;
sections.forEach(function(s){
// 用 rect 计算,兼容 section 位于 flex 子容器内的情况
var top = s.el.getBoundingClientRect().top + window.scrollY;
if (top <= y) activeId = s.id;
});
allLinks.forEach(function(a){ a.classList.remove('active'); });
(map[activeId] || []).forEach(function(a){ a.classList.add('active'); });
// 侧栏自身可滚动时,保证激活项可见
var activeLink = (map[activeId] || [])[0];
if (activeLink && activeLink.closest) {
var side = activeLink.closest('.side-nav');
if (side && side.scrollHeight > side.clientHeight) {
var lr = activeLink.getBoundingClientRect();
var sr = side.getBoundingClientRect();
if (lr.top < sr.top) side.scrollTop -= (sr.top - lr.top) + 8;
else if (lr.bottom > sr.bottom) side.scrollTop += (lr.bottom - sr.bottom) + 8;
}
}
}
window.addEventListener('scroll', onScroll, {passive: true});
window.addEventListener('resize', onScroll);
onScroll();
})();
// ===== 移动端分类抽屉 =====
(function(){
var drawer = document.getElementById('cat-drawer');
var list = document.getElementById('cat-drawer-list');
var closeBtn = document.getElementById('cat-drawer-close');
var triggerMobile = document.getElementById('cat-trigger-mobile');
var fab = document.getElementById('cat-fab');
var countEl = document.getElementById('cat-fab-count');
if (!drawer || !list) return;
function openDrawer(){
drawer.classList.add('open');
document.body.classList.add('drawer-open'); // 锁背景滚动
}
function closeDrawer(){
drawer.classList.remove('open');
document.body.classList.remove('drawer-open');
}
if (triggerMobile) triggerMobile.addEventListener('click', openDrawer);
if (fab) fab.addEventListener('click', openDrawer);
if (closeBtn) closeBtn.addEventListener('click', closeDrawer);
// 点遮罩空白处关闭(不在 panel 内即可)
drawer.addEventListener('click', function(e){
if (e.target === drawer) closeDrawer();
});
// 如果列表已由模板渲染(带 cat_meta),只需绑定关闭事件
var existingItems = list.querySelectorAll('li a[href^="#"]');
existingItems.forEach(function(link){
link.addEventListener('click', closeDrawer);
});
// 兜底:没有模板渲染时,从 .cat-nav 同步
if (!existingItems.length) {
var navLinks = document.querySelectorAll('.cat-nav a[href^="#"]');
if (countEl) countEl.textContent = navLinks.length;
navLinks.forEach(function(a){
var li = document.createElement('li');
var link = document.createElement('a');
link.href = a.getAttribute('href');
link.textContent = a.textContent;
var arrow = document.createElement('span');
arrow.className = 'arrow';
arrow.textContent = '→';
link.appendChild(arrow);
li.appendChild(link);
list.appendChild(li);
link.addEventListener('click', closeDrawer);
});
}
})();
// ===== 导航卡片点击埋点:sendBeacon,不影响原 href 跳转 =====
(function(){
function bindNavClick(card){
if (!card || card.__navBound) return;
card.__navBound = true;
card.addEventListener('click', function(){
var navId = card.getAttribute('data-nav-id');
if (!navId) return;
// 本机 localStorage 立即 +1,「我常访问」下次进页面就生效
try { if (window.__wkMineBump) window.__wkMineBump(navId); } catch(e) {}
try {
if (navigator.sendBeacon) {
navigator.sendBeacon('/site/nav/' + navId + '/click');
} else {
// 兜底:fetch keepalive POST
fetch('/site/nav/' + navId + '/click', {method:'POST', keepalive:true, credentials:'same-origin'});
}
} catch(e) { /* 静默失败,不影响跳转 */ }
});
}
function bindAll(){
document.querySelectorAll('.card[data-nav-id]').forEach(bindNavClick);
}
window.__wkBindNavClick = bindNavClick;
window.__wkBindNavAll = bindAll;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bindAll);
} else {
bindAll();
}
})();
// ===== 「我常访问」:localStorage 本地累计 + 服务端匿名榜合并 =====
(function(){
var KEY = 'wk_mine_clicks';
var MIN_ITEMS = 2; // 冷启动:至少点过 2 个不同网址才展示
var MAX_ITEMS = 8;
function load(){
try { return JSON.parse(localStorage.getItem(KEY) || '{}') || {}; }
catch(e) { return {}; }
}
function save(m){
try { localStorage.setItem(KEY, JSON.stringify(m)); } catch(e) {}
}
window.__wkMineBump = function(navId){
if (!navId) return;
var m = load();
m[navId] = (m[navId] || 0) + 1;
save(m);
};
function findSourceCard(id){
var all = document.querySelectorAll('.card[data-nav-id="' + id + '"]');
for (var i = 0; i < all.length; i++) {
if (!all[i].closest('#cat-mine')) return all[i];
}
return null;
}
function addCatNavEntry(){
var inner = document.getElementById('cat-nav-inner');
if (!inner || inner.querySelector('.cat-nav-mine')) return;
var a = document.createElement('a');
a.href = '#cat-mine';
a.className = 'cat-nav-mine';
a.textContent = '★ 我常访问';
inner.insertBefore(a, inner.firstChild);
}
function addDrawerEntry(){
var panel = document.querySelector('.cat-drawer-panel');
if (!panel || panel.querySelector('.cat-drawer-mine')) return;
var sec = document.createElement('div');
sec.className = 'cat-drawer-section';
sec.innerHTML =
'<div class="cat-drawer-title"><i class="layui-icon layui-icon-star-fill"></i> 我的</div>' +
'<ul class="cat-drawer-list"><li>' +
'<a href="#cat-mine" class="cat-drawer-mine">' +
'<i class="layui-icon cat-icon layui-icon-star-fill"></i>' +
'<span>我常访问</span><span class="arrow">→</span></a></li></ul>';
panel.insertBefore(sec, panel.children[1] || null);
}
// 左侧侧栏里补一个「我常访问」入口(桌面端)
function addSideNavEntry(){
var nav = document.getElementById('side-nav');
if (!nav || nav.querySelector('.side-mine')) return;
var block = document.createElement('div');
block.className = 'side-block side-mine-block';
block.innerHTML =
'<div class="side-title"><i class="layui-icon layui-icon-star-fill"></i> 我的</div>' +
'<ul class="side-list"><li>' +
'<a href="#cat-mine" class="side-mine" data-side-anchor="cat-mine">' +
'<i class="layui-icon side-ico layui-icon-star-fill"></i>' +
'<span class="side-txt">我常访问</span></a></li></ul>';
nav.insertBefore(block, nav.firstChild);
}
function removeEntries(){
var n = document.querySelector('.cat-nav-mine');
if (n && n.parentNode) n.parentNode.removeChild(n);
var d = document.querySelector('.cat-drawer-mine');
if (d) {
var s = d.closest('.cat-drawer-section');
if (s && s.parentNode) s.parentNode.removeChild(s);
}
var sb = document.querySelector('.side-mine-block');
if (sb && sb.parentNode) sb.parentNode.removeChild(sb);
}
function render(){
var main = document.querySelector('main.content') || document.querySelector('main');
if (!main) return;
var local = load();
var score = {};
// 1) 服务端已经渲染出来的个人卡片(带 data-mine 加权分)
var sec = document.getElementById('cat-mine');
if (sec) {
sec.querySelectorAll('.card[data-nav-id]').forEach(function(c){
var id = c.getAttribute('data-nav-id');
var v = parseFloat(c.getAttribute('data-mine') || '0') || 0;
if (v > (score[id] || 0)) score[id] = v;
});
}
// 2) 本机 localStorage(含服务端还没同步到的点击)
Object.keys(local).forEach(function(id){
var v = local[id] | 0;
if (v > (score[id] || 0)) score[id] = v;
});
var ranked = Object.keys(score)
.filter(function(id){ return (score[id] || 0) > 0; })
.sort(function(a, b){ return (score[b] - score[a]) || (a - b); })
.slice(0, MAX_ITEMS);
if (ranked.length < MIN_ITEMS) {
if (sec) sec.style.display = 'none';
return;
}
if (!sec) {
sec = document.createElement('section');
sec.className = 'section section-mine';
sec.id = 'cat-mine';
sec.innerHTML =
'<div class="section-header">' +
'<h2><i class="layui-icon layui-icon-star-fill"></i> 我常访问</h2>' +
'<div class="meta">只在这台设备上统计 · <a href="javascript:;" id="clear-mine">清除</a></div>' +
'</div><div class="grid" id="mine-grid"></div>';
main.insertBefore(sec, main.firstChild);
addCatNavEntry();
addDrawerEntry();
addSideNavEntry();
}
sec.style.display = '';
var grid = sec.querySelector('.grid');
grid.innerHTML = '';
ranked.forEach(function(id){
var src = findSourceCard(id);
if (!src) return;
var clone = src.cloneNode(true);
clone.classList.add('card-mine');
clone.classList.remove('card-top');
clone.setAttribute('data-mine', score[id]);
var vc = clone.querySelector('.visit-count');
if (vc) vc.textContent = '我点过 ' + Math.round(score[id]) + ' 次';
grid.appendChild(clone);
if (window.__wkBindNavClick) window.__wkBindNavClick(clone);
});
}
// 清除我的记录:清 localStorage + 通知服务端删匿名点击
document.addEventListener('click', function(e){
var t = e.target;
if (!t) return;
if (t.id !== 'clear-mine' && !(t.closest && t.closest('#clear-mine'))) return;
e.preventDefault();
try { localStorage.removeItem(KEY); } catch(err) {}
try {
if (navigator.sendBeacon) navigator.sendBeacon('/site/api/my-clear');
else fetch('/site/api/my-clear', {method:'POST', keepalive:true, credentials:'same-origin'});
} catch(err) {}
removeEntries();
var sec = document.getElementById('cat-mine');
if (sec && sec.parentNode) sec.parentNode.removeChild(sec);
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', render);
} else {
render();
}
})();
// ===== 全局搜索:前端实时过滤卡片,并高亮分类 Tab =====
(function(){
var form = document.getElementById('site-search');
var input = document.getElementById('search-input');
if (!input) return;
function filter(keyword){
keyword = (keyword || '').trim().toLowerCase();
var cards = document.querySelectorAll('.grid .card');
var sections = document.querySelectorAll('section.section');
cards.forEach(function(card){
var text = (card.textContent || '').toLowerCase();
if (!keyword || text.indexOf(keyword) !== -1) {
card.style.display = '';
} else {
card.style.display = 'none';
}
});
// 隐藏整节没有可见卡片的分类
sections.forEach(function(sec){
var visible = sec.querySelectorAll('.card:not([style*="display: none"])').length;
sec.style.display = visible ? '' : 'none';
});
// 同步左侧侧栏 / 抽屉:刷新计数,并隐藏没有命中的分类
document.querySelectorAll('a[data-side-anchor]').forEach(function(a){
var id = a.getAttribute('data-side-anchor');
var sec = document.getElementById(id);
var li = a.parentNode;
if (!sec || !li) return; // 本页无对应 section(空分类 / 非首页)保持原样
var n = sec.querySelectorAll('.card:not([style*="display: none"])').length;
var cnt = a.querySelector('.side-count');
if (cnt) cnt.textContent = n ? String(n) : '';
li.style.display = n ? '' : 'none';
});
// 无结果空态
var empty = document.getElementById('search-empty');
if (empty) {
var totalVisible = document.querySelectorAll('.grid .card:not([style*="display: none"])').length;
if (keyword && totalVisible === 0) {
empty.innerHTML = '<span class="ico">🔍</span>未找到与「' + keyword + '」匹配的网址';
empty.hidden = false;
} else {
empty.hidden = true;
}
}
}
// 输入防抖,避免每次按键都遍历全部卡片
var t;
input.addEventListener('input', function(){
clearTimeout(t);
var v = input.value;
t = setTimeout(function(){ filter(v); }, 120);
});
form.addEventListener('submit', function(e){
// 如果当前在首页直接本地过滤,否则提交到首页
var isHome = window.location.pathname === '/' || window.location.pathname === '/site/';
if (isHome) {
e.preventDefault();
filter(input.value);
}
});
// 首页:隐藏没有对应 section 的「空分类」项(侧栏 + 抽屉)
var sideNav = document.getElementById('side-nav');
if (sideNav && sideNav.getAttribute('data-home') === '1') {
document.querySelectorAll('a[data-side-anchor]').forEach(function(a){
var sec = document.getElementById(a.getAttribute('data-side-anchor'));
if (!sec && a.parentNode) a.parentNode.style.display = 'none';
});
}
// 初始化:填充侧栏计数;同时读取 URL 的 ?q=xxx 自动过滤
var params = new URLSearchParams(window.location.search);
var q = params.get('q');
if (q) input.value = q;
filter(q || '');
})();
// ===== 侧栏「切换主题」:弹出下拉菜单 =====
(function(){
var btn = document.getElementById('side-theme-toggle');
var menu = document.getElementById('side-theme-menu');
if (!btn || !menu) return;
function closeAll(){
document.querySelectorAll('.theme-menu.open').forEach(function(m){ m.classList.remove('open'); });
}
btn.addEventListener('click', function(e){
e.preventDefault();
e.stopPropagation();
var wasOpen = menu.classList.contains('open');
closeAll();
if (!wasOpen) menu.classList.add('open');
});
menu.addEventListener('click', function(e){
var li = e.target.closest('li[data-theme]');
if (!li) return;
e.stopPropagation();
applyTheme(li.getAttribute('data-theme'), true);
closeAll();
});
document.addEventListener('click', closeAll);
document.addEventListener('keydown', function(e){ if (e.key === 'Escape') closeAll(); });
})();
// ===== 抽屉内「切换主题」:弹出下拉菜单 =====
(function(){
var btn = document.getElementById('drawer-theme-toggle');
var menu = document.getElementById('drawer-theme-menu');
if (!btn || !menu) return;
function closeAll(){
document.querySelectorAll('.theme-menu.open').forEach(function(m){ m.classList.remove('open'); });
}
btn.addEventListener('click', function(e){
e.preventDefault();
e.stopPropagation();
var wasOpen = menu.classList.contains('open');
closeAll();
if (!wasOpen) menu.classList.add('open');
});
menu.addEventListener('click', function(e){
var li = e.target.closest('li[data-theme]');
if (!li) return;
e.stopPropagation();
applyTheme(li.getAttribute('data-theme'), true);
closeAll();
// 关闭抽屉
var drawer = document.getElementById('cat-drawer');
if (drawer) drawer.classList.remove('open');
});
document.addEventListener('click', closeAll);
document.addEventListener('keydown', function(e){ if (e.key === 'Escape') closeAll(); });
})();
</script>
</body>
</html>