Files
pear-admin-flask/templates/public/base.html
T
bwstudio 9ba96bcc3e style(public): 前台导航页响应式适配 + 暗色模式 + 锚点目录
主要改动
- templates/public/base.html
  - 增加 3 套响应式断点:≤900px(平板)、≤600px(手机)、≤360px(小屏)
  - topbar 在小屏下压缩 padding/字号,actions 自适应换行
  - grid 卡片在手机端最小 160px,小屏单列
  - 卡片字号、间距在手机端缩放
  - 增加 sticky 分类目录条占位
  - 增加回到顶部按钮(滚动后显示)
  - 增加暗色模式 CSS(prefers-color-scheme + data-theme 手动覆盖)和主题切换按钮
  - 补充 theme-color、viewport 等移动端 meta
- templates/public/index.html
  - 输出 5 个分类的 sticky 目录导航(点击锚点跳转)
  - 每个 section 设置 id=cat-N 锚点
  - 卡片 title/url 增加 title 提示
- templates/public/category.html
  - 目录条加返回首页 + 当前分类名
  - section 锚点统一为 cat-detail

验证
- curl 抓取 /site/ 响应 200,HTML 中 10 项响应式/交互特性检查全部通过
- 服务器本地运行无异常
2026-09-05 15:33:45 +08:00

345 lines
13 KiB
HTML

<!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('BWStudio · 导航') }}{% endblock %}</title>
<meta name="description" content="BWStudio 公开导航聚合页:精选开发工具、AI、设计资源、效率工具等优质站点。">
<meta property="og:title" content="{{ site_name|default('BWStudio · 导航') }}">
<meta property="og:description" content="精选优质站点,按分类聚合展示。">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/layui@2.9.7/dist/css/layui.css">
<style>
:root {
--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="dark"] {
--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);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--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);
}
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
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 .container { max-width: 1200px; 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; }
.topbar .actions { margin-top: 18px; display: flex; flex-wrap: wrap; gap: 8px; }
.topbar .actions a, .topbar .actions button {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 6px 14px;
background: rgba(255,255,255,0.18);
border: 1px solid rgba(255,255,255,0.35);
color: var(--topbar-text);
border-radius: 999px;
font-size: 13px;
text-decoration: none;
cursor: pointer;
transition: background 0.2s;
font-family: inherit;
}
.topbar .actions a:hover, .topbar .actions button:hover { background: rgba(255,255,255,0.32); }
.container { max-width: 1200px; 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: 1200px;
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; }
/* ===== Section ===== */
.section { margin: 32px 0; scroll-margin-top: 70px; }
.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(260px, 1fr));
gap: 16px;
}
.card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 10px;
padding: 18px;
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; flex-direction: column; gap: 6px;
min-width: 0;
}
.card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-hover);
border-color: var(--brand);
}
.card:active { transform: translateY(0); } /* 移动端点击反馈 */
.card .title-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.card .icon-wrap {
width: 36px; height: 36px;
background: rgba(22, 186, 170, 0.1);
color: var(--brand);
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 18px;
flex-shrink: 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 .desc {
font-size: 13px; color: var(--text-muted);
line-height: 1.5;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
min-height: 38px;
}
.card .url {
font-size: 12px; color: var(--text-soft);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
direction: rtl;
text-align: left;
}
.empty {
text-align: center; padding: 48px 0; color: var(--text-muted);
}
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; }
/* ===== 回到顶部 ===== */
.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(220px, 1fr)); }
.topbar h1 { font-size: 30px; }
}
/* ===== 响应式:手机 ===== */
@media (max-width: 600px) {
body { font-size: 14px; }
.container { padding: 0 14px; }
.topbar { padding: 32px 0 28px; }
.topbar .container { padding: 0 14px; }
.topbar h1 { font-size: 24px; }
.topbar .sub { font-size: 13px; }
.topbar .actions a, .topbar .actions button { padding: 5px 11px; font-size: 12px; }
.cat-nav-inner { padding: 6px 14px; }
.cat-nav a { padding: 5px 11px; font-size: 12px; }
.section { margin: 22px 0; scroll-margin-top: 60px; }
.section-header h2 { font-size: 17px; }
.section-header .meta { font-size: 12px; }
.grid {
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 10px;
}
.card { padding: 12px; border-radius: 8px; }
.card .title { font-size: 14px; }
.card .desc { font-size: 12px; min-height: 32px; }
.card .icon-wrap { width: 30px; height: 30px; font-size: 16px; }
.to-top { right: 14px; bottom: 14px; width: 38px; height: 38px; }
}
@media (max-width: 360px) {
.grid { grid-template-columns: 1fr; }
}
</style>
{% block extra_head %}{% endblock %}
</head>
<body>
<header class="topbar">
<div class="container">
<h1>{{ site_name|default('BWStudio · 导航') }}</h1>
<div class="sub">{% block subtitle %}精选优质站点,按分类聚合展示 · 共 {{ total|default(0) }} 个链接{% endblock %}</div>
<div class="actions">
<a href="{{ url_for('public_nav.index') }}">🏠 首页</a>
<a href="/admin/login">🔐 登录后台</a>
<button type="button" id="theme-toggle" title="切换主题" aria-label="切换主题">🌗 主题</button>
</div>
</div>
</header>
{% block cat_nav %}{% endblock %}
<main class="container">
{% block content %}{% endblock %}
</main>
<footer class="site-footer">
© 2026 BWStudio · 基于 <a href="https://gitea.bwhome.top/bwadmin/pear-admin-flask">Pear Admin Flask</a> 构建
</footer>
<button class="to-top" id="to-top" aria-label="回到顶部" title="回到顶部"></button>
{% block extra_js %}{% endblock %}
<script>
// 暗色模式:localStorage > data-theme > 系统
(function(){
var saved = localStorage.getItem('bw-theme');
if (saved === 'dark' || saved === 'light') {
document.documentElement.setAttribute('data-theme', saved);
}
var btn = document.getElementById('theme-toggle');
if (btn) {
btn.addEventListener('click', function(){
var cur = document.documentElement.getAttribute('data-theme');
var sysDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
var isDark = cur ? (cur === 'dark') : sysDark;
var next = isDark ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('bw-theme', next);
});
}
// 回到顶部
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
var navLinks = document.querySelectorAll('.cat-nav a[href^="#"]');
if (navLinks.length) {
var sections = [];
navLinks.forEach(function(a){
var id = a.getAttribute('href').slice(1);
var el = document.getElementById(id);
if (el) sections.push({link: a, el: el});
});
function onScroll(){
var y = window.scrollY + 100;
var active = sections[0];
for (var i = 0; i < sections.length; i++) {
if (sections[i].el.offsetTop <= y) active = sections[i];
}
navLinks.forEach(function(a){ a.classList.remove('active'); });
if (active) active.link.classList.add('active');
}
window.addEventListener('scroll', onScroll, {passive: true});
onScroll();
}
})();
</script>
</body>
</html>