🚚 移动前端

This commit is contained in:
shufeng2012 committed 2026-08-08 19:09:36 +08:00
1 parent c8f2152d0a
commit b851a96a66
57 files changed
+208 -412

No files matched your search

File renamed without changes.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+208 -183
View File
@@ -1,202 +1,227 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>fsys 测试页</title> <meta name="description" content="DNFS 树状笔记:以树状结构组织知识的笔记社区,支持 Markdown、链接卡片、图片与文件分享。">
<style> <meta name="theme-color" media="(prefers-color-scheme: light)" content="#fdf6ee">
* { box-sizing: border-box; margin: 0; padding: 0; } <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1e1712">
body { font-family: system-ui, sans-serif; background: #0f1419; color: #e6e6e6; padding: 32px 16px; } <link rel="icon" href="favicon.ico">
.wrap { max-width: 860px; margin: 0 auto; } <link rel="stylesheet" href="css/theme.css">
h1 { font-size: 22px; margin-bottom: 4px; } <link rel="stylesheet" href="css/index.css">
.sub { color: #8b98a5; font-size: 13px; margin-bottom: 24px; } <title>DNFS 树状笔记 — 让知识自然成体系</title>
.card { background: #1a2027; border: 1px solid #2a3441; border-radius: 10px; padding: 16px; margin-bottom: 14px; }
.card h2 { font-size: 14px; color: #9fb3c8; margin-bottom: 10px; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
td { padding: 7px 8px; border-bottom: 1px solid #232d38; vertical-align: top; }
td.name { width: 34%; color: #d7e2ec; }
td.st { width: 90px; font-weight: 600; }
.ok { color: #4ade80; } .fail { color: #f87171; } .pending { color: #8b98a5; }
td.detail { color: #8b98a5; font-family: ui-monospace, monospace; font-size: 12px; word-break: break-all; }
button { background: #2563eb; color: #fff; border: 0; border-radius: 8px; padding: 10px 22px;
font-size: 14px; cursor: pointer; margin-bottom: 18px; }
button:hover { background: #1d4ed8; }
.links a { color: #60a5fa; text-decoration: none; margin-right: 14px; font-size: 13px; }
.links a:hover { text-decoration: underline; }
#summary { font-size: 14px; margin-bottom: 14px; }
</style>
</head> </head>
<body> <body>
<header class="header">
<div class="wrap"> <div class="wrap">
<h1>fsys 测试页</h1> <div class="header-inner">
<div class="sub">C++ 静态文件托管服务 · 路由/安全功能自检</div> <h1 id="logo">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="10.51" x2="15.42" y2="6.51"/><line x1="8.59" y1="13.49" x2="15.42" y2="17.49"/></svg>
<span>DNFS 树状笔记</span>
</h1>
<nav class="right-group" aria-label="用户操作">
<img src="favicon.ico" alt="用户头像" id="avatar" style="display:none">
<b id="name" style="display:none"></b>
<button id="enterBtn" class="btn btn-primary" style="display:none">进入笔记</button>
<button id="logoutBtn" class="btn btn-ghost" style="display:none">退出</button>
<button id="loginBtn" class="btn btn-primary">登录</button>
<button id="registerBtn" class="btn btn-ghost">注册</button>
</nav>
</div>
</div>
</header>
<button onclick="runAll()">▶ 运行全部测试</button> <main id="main-page">
<div id="summary"></div> <!-- ===== Hero ===== -->
<section class="hero">
<div class="card"> <div class="hero-bg" aria-hidden="true">
<h2>API 自检</h2> <svg class="hero-tree" viewBox="0 0 800 440" fill="none">
<table id="tbl"> <defs>
<tr data-t="health"><td class="name">GET /health</td><td class="st pending">—</td><td class="detail"></td></tr> <radialGradient id="glow" cx="50%" cy="50%" r="50%">
<tr data-t="filelist_root"><td class="name">GET /api/filelist(根目录)</td><td class="st pending">—</td><td class="detail"></td></tr> <stop offset="0%" stop-color="currentColor" stop-opacity="0.18"/>
<tr data-t="filelist_sub"><td class="name">GET /api/filelist?path=test%2F(编码子目录)</td><td class="st pending">—</td><td class="detail"></td></tr> <stop offset="100%" stop-color="currentColor" stop-opacity="0"/>
<tr data-t="filelist_traverse"><td class="name">GET /api/filelist?path=..%2F..(遍历应 403)</td><td class="st pending">—</td><td class="detail"></td></tr> </radialGradient>
<tr data-t="static_file"><td class="name">GET /test/hello.txt(静态文件)</td><td class="st pending">—</td><td class="detail"></td></tr> </defs>
<tr data-t="video_range"><td class="name">GET /video/test/sample.mp4(Range 206/416)</td><td class="st pending">—</td><td class="detail"></td></tr> <circle cx="400" cy="210" r="180" fill="url(#glow)"/>
<tr data-t="download"><td class="name">GET /test/hello.txt?download=true(附件头)</td><td class="st pending">—</td><td class="detail"></td></tr> <g class="tree-branches" stroke="currentColor" stroke-linecap="round">
<tr data-t="utf8_path"><td class="name">GET 中文文件名(URL 解码)</td><td class="st pending">—</td><td class="detail"></td></tr> <path pathLength="100" d="M400 400 L400 295" stroke-width="2.5"/>
<tr data-t="hash"><td class="name">POST /api/hash(sha256("test123"))</td><td class="st pending">—</td><td class="detail"></td></tr> <path pathLength="100" d="M400 295 C 365 265, 310 240, 260 205" stroke-width="2"/>
<tr data-t="hash_bad"><td class="name">POST /api/hash 非法输入(应 400)</td><td class="st pending">—</td><td class="detail"></td></tr> <path pathLength="100" d="M400 295 C 435 265, 490 240, 540 205" stroke-width="2"/>
<tr data-t="check_user"><td class="name">POST /api/check_user(结构校验)</td><td class="st pending">—</td><td class="detail"></td></tr> <path pathLength="100" d="M260 205 C 238 178, 210 152, 185 120" stroke-width="1.5"/>
<tr data-t="favicon_private"><td class="name">GET /api/favicon 内网地址(SSRF 应 403)</td><td class="st pending">—</td><td class="detail"></td></tr> <path pathLength="100" d="M260 205 C 278 178, 300 152, 318 120" stroke-width="1.5"/>
<tr data-t="favicon_public"><td class="name">GET /api/favicon 公网地址(需外网)</td><td class="st pending">—</td><td class="detail"></td></tr> <path pathLength="100" d="M540 205 C 518 178, 496 152, 478 120" stroke-width="1.5"/>
<tr data-t="headers"><td class="name">安全响应头(CSP/X-Frame-Options…)</td><td class="st pending">—</td><td class="detail"></td></tr> <path pathLength="100" d="M540 205 C 562 178, 590 152, 615 120" stroke-width="1.5"/>
<tr data-t="notfound"><td class="name">GET /不存在的路径(应 404)</td><td class="st pending">—</td><td class="detail"></td></tr> <path pathLength="100" d="M185 120 C 172 100, 160 82, 150 60" stroke-width="1"/>
</table> <path pathLength="100" d="M318 120 C 328 100, 338 82, 345 60" stroke-width="1"/>
<path pathLength="100" d="M478 120 C 468 100, 458 82, 452 60" stroke-width="1"/>
<path pathLength="100" d="M615 120 C 628 100, 640 82, 650 60" stroke-width="1"/>
</g>
<g class="tree-nodes" fill="currentColor">
<circle cx="400" cy="400" r="7" opacity=".9"/>
<circle cx="400" cy="295" r="5.5"/>
<circle cx="260" cy="205" r="5"/>
<circle cx="540" cy="205" r="5"/>
<circle cx="185" cy="120" r="4"/>
<circle cx="318" cy="120" r="4"/>
<circle cx="478" cy="120" r="4"/>
<circle cx="615" cy="120" r="4"/>
<circle cx="150" cy="60" r="3" opacity=".65"/>
<circle cx="345" cy="60" r="3" opacity=".65"/>
<circle cx="452" cy="60" r="3" opacity=".65"/>
<circle cx="650" cy="60" r="3" opacity=".65"/>
</g>
</svg>
</div> </div>
<div class="card links"> <div class="wrap hero-inner">
<h2>手动验证链接</h2> <p class="hero-badge"><span class="badge-dot"></span>树状结构 · Markdown · 多媒体分享</p>
<a href="/api/filelist?path=" target="_blank">filelist 根</a> <h2 class="hero-title">让知识像树一样<br><span class="accent-text">自然生长</span></h2>
<a href="/api/filelist?path=test" target="_blank">filelist test/</a> <p class="hero-sub">每个节点都可以生长出子节点,像树枝一样层层延展<br>让零散的想法汇聚成体系</p>
<a href="/test/hello.txt" target="_blank">hello.txt</a> <div class="hero-actions">
<a href="/test/hello.txt?download=true" target="_blank">下载模式</a> <button class="btn btn-primary btn-lg" data-cta>开始记录</button>
<a href="/health" target="_blank">health</a> <a class="btn btn-ghost btn-lg" href="#features">了解更多</a>
</div> </div>
</div> </div>
<script> <a class="scroll-hint" href="#features" aria-label="下滑了解特性">
const EXPECT_HASH = 'ecd71870d1963316a97e3ac3408c9835ad8cf0f3c1bc703527c30265534f75ae'; <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</a>
</section>
function setRow(key, ok, detail) { <!-- ===== 核心特性 ===== -->
const tr = document.querySelector(`tr[data-t="${key}"]`); <section id="features" class="section features-section">
if (!tr) return; <div class="wrap">
const st = tr.children[1], dt = tr.children[2]; <p class="section-eyebrow">核心特性</p>
st.textContent = ok ? 'PASS' : 'FAIL'; <h2 class="section-title">为什么选择树状笔记</h2>
st.className = 'st ' + (ok ? 'ok' : 'fail'); <p class="section-sub">从灵感到体系,让知识在树上自然生长</p>
dt.textContent = detail || ''; <div class="features">
} <div class="feature-card">
<div class="feature-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="10.51" x2="15.42" y2="6.51"/><line x1="8.59" y1="13.49" x2="15.42" y2="17.49"/></svg>
</div>
<div class="feature-body">
<h3>树状结构组织</h3>
<p>每个节点都能生长出子节点,像树枝般层层延展,让零散的想法自然汇聚成体系。</p>
</div>
<span class="feature-tag">无限层级</span>
</div>
<div class="feature-card">
<div class="feature-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
</div>
<div class="feature-body">
<h3>Markdown 写作</h3>
<p>原生支持 Markdown 渲染,代码块、引用、列表一应俱全,专注内容本身。</p>
</div>
<span class="feature-tag">专注写作</span>
</div>
<div class="feature-card">
<div class="feature-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
</div>
<div class="feature-body">
<h3>多媒体分享</h3>
<p>链接卡片、图片直显、文件下载,多种内容形态一站承载,分享更自由。</p>
</div>
<span class="feature-tag">自由分享</span>
</div>
<div class="feature-card">
<div class="feature-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
</div>
<div class="feature-body">
<h3>深色模式</h3>
<p>跟随系统偏好自动切换明暗主题,深夜记录也不刺眼,日夜皆宜。</p>
</div>
<span class="feature-tag">日夜皆宜</span>
</div>
</div>
</div>
</section>
async function j(url, opt) { <!-- ===== 关于本站 ===== -->
const res = await fetch(url, opt); <section class="section about-section">
const text = await res.text(); <div class="wrap">
let json = null; <p class="section-eyebrow">我们的理念</p>
try { json = JSON.parse(text); } catch (e) {} <h2 class="section-title">关于本站</h2>
return { res, text, json }; <div class="about-panel">
} <svg class="about-deco" viewBox="0 0 200 200" fill="none" aria-hidden="true">
<circle cx="100" cy="100" r="90" stroke="currentColor" stroke-width=".6" opacity=".15"/>
<circle cx="100" cy="100" r="60" stroke="currentColor" stroke-width=".5" opacity=".10"/>
<circle cx="100" cy="100" r="30" stroke="currentColor" stroke-width=".4" opacity=".08"/>
</svg>
<p class="site-intro">
DNFS 树状笔记是一个以树状结构组织内容的笔记社区:每个节点都可以生长出子节点,
像树枝一样层层延展,让知识自然成体系。支持 Markdown 文本、链接卡片、图片与文件分享,
注册登录后即可创建属于你自己的知识树。
</p>
<ul class="about-stats">
<li><b>∞</b><span>节点层级无上限</span></li>
<li><b>MD</b><span>原生 Markdown 渲染</span></li>
<li><b>4+</b><span>内容形态一站承载</span></li>
</ul>
</div>
<nav class="quick-links" aria-label="快捷链接">
<a href="allcolor.html">调色板</a>
<a href="games/">小游戏</a>
<a href="LICENSE.html">用户使用条例</a>
</nav>
</div>
</section>
const tests = { <!-- ===== 结尾 CTA ===== -->
async health() { <section class="section cta-section" aria-label="立即开始">
const { res, json } = await j('/health'); <div class="wrap">
const ok = res.status === 200 && json && json.status === 'ok'; <div class="cta-band">
setRow('health', ok, `HTTP ${res.status} ${JSON.stringify(json)}`); <svg class="cta-tree" viewBox="0 0 400 200" fill="none" aria-hidden="true">
}, <g stroke="currentColor" stroke-width="1.2" stroke-linecap="round" opacity=".6">
async filelist_root() { <path d="M200 190 L200 140"/>
const { res, json } = await j('/api/filelist?path='); <path d="M200 140 C 175 120, 145 105, 120 85"/>
const ok = res.status === 200 && json && json.success === true && Array.isArray(json.items); <path d="M200 140 C 225 120, 255 105, 280 85"/>
setRow('filelist_root', ok, `HTTP ${res.status}, ${json && json.items ? json.items.length + ' 项' : '无 items'}`); <path d="M120 85 C 105 65, 90 50, 80 30"/>
}, <path d="M120 85 C 135 65, 148 50, 155 30"/>
async filelist_sub() { <path d="M280 85 C 265 65, 252 50, 245 30"/>
const { res, json } = await j('/api/filelist?path=' + encodeURIComponent('test')); <path d="M280 85 C 295 65, 310 50, 320 30"/>
const hasHello = json && Array.isArray(json.items) && json.items.some(i => i.name === 'hello.txt'); </g>
setRow('filelist_sub', res.status === 200 && hasHello, `HTTP ${res.status}, hello.txt ${hasHello ? '可见' : '缺失'}`); <g fill="currentColor" opacity=".5">
}, <circle cx="200" cy="190" r="4"/>
async filelist_traverse() { <circle cx="200" cy="140" r="3"/>
const { res } = await j('/api/filelist?path=' + encodeURIComponent('../../')); <circle cx="120" cy="85" r="2.5"/>
setRow('filelist_traverse', res.status === 403, `HTTP ${res.status}(期望 403)`); <circle cx="280" cy="85" r="2.5"/>
}, <circle cx="80" cy="30" r="2"/>
async static_file() { <circle cx="155" cy="30" r="2"/>
const { res, text } = await j('/test/hello.txt'); <circle cx="245" cy="30" r="2"/>
const ok = res.status === 200 && text.includes('hello fsys'); <circle cx="320" cy="30" r="2"/>
setRow('static_file', ok, `HTTP ${res.status}, ${(res.headers.get('content-type') || '').split(';')[0]}`); </g>
}, </svg>
async video_range() { <h2 class="cta-title">准备好让知识生长了吗?</h2>
const full = await fetch('/video/test/sample.mp4'); <p class="cta-sub">注册一个账号,种下你的第一棵知识树</p>
const partial = await fetch('/video/test/sample.mp4', { headers: { Range: 'bytes=0-99' } }); <button class="btn btn-cta btn-lg" data-cta>立即开始</button>
const bad = await fetch('/video/test/sample.mp4', { headers: { Range: 'bytes=999999-' } }); </div>
const cr = partial.headers.get('content-range') || ''; </div>
const ok = full.status === 200 && full.headers.get('accept-ranges') === 'bytes' </section>
&& partial.status === 206 && cr.startsWith('bytes 0-99/') </main>
&& partial.headers.get('content-length') === '100'
&& bad.status === 416;
setRow('video_range', ok, `full=${full.status}, range=${partial.status} ${cr}, bad=${bad.status}(期望 416)`);
},
async download() {
const { res } = await j('/test/hello.txt?download=true');
const cd = res.headers.get('content-disposition') || '';
setRow('download', res.status === 200 && cd.includes('attachment'), `content-disposition: ${cd || '(无)'}`);
},
async utf8_path() {
const { res, text } = await j('/test/' + encodeURIComponent('中文文件.txt'));
const ok = res.status === 200 && text.includes('中文内容');
setRow('utf8_path', ok, `HTTP ${res.status}(URL 解码生效=${ok})`);
},
async hash() {
const { res, json } = await j('/api/hash', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: 'test123' })
});
const ok = res.status === 200 && json && json.hash === EXPECT_HASH;
setRow('hash', ok, `HTTP ${res.status}, hash ${ok ? '匹配' : '不匹配'}`);
},
async hash_bad() {
const { res } = await j('/api/hash', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: 12345 })
});
setRow('hash_bad', res.status === 400, `HTTP ${res.status}(期望 400)`);
},
async check_user() {
const { res, json } = await j('/api/check_user', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'fsys-test@example.com' })
});
// db 可能不可用:只要返回结构合法(success 字段存在)即 PASS
const ok = [200, 500].includes(res.status) && json && typeof json.success === 'boolean';
setRow('check_user', ok, `HTTP ${res.status} ${JSON.stringify(json)}`);
},
async favicon_private() {
const { res } = await j('/api/favicon?url=' + encodeURIComponent('http://127.0.0.1:8005/'));
setRow('favicon_private', res.status === 403, `HTTP ${res.status}(期望 403 forbidden host)`);
},
async favicon_public() {
try {
const { res } = await j('/api/favicon?url=' + encodeURIComponent('https://www.baidu.com'));
const ct = res.headers.get('content-type') || '';
setRow('favicon_public', res.status === 200 && ct.startsWith('image/'), `HTTP ${res.status} ${ct}`);
} catch (e) {
setRow('favicon_public', false, '网络不可达: ' + e.message);
}
},
async headers() {
const res = await fetch('/health');
const need = ['content-security-policy', 'x-frame-options', 'x-content-type-options',
'referrer-policy', 'permissions-policy'];
const missing = need.filter(h => !res.headers.get(h));
setRow('headers', missing.length === 0, missing.length ? '缺失: ' + missing.join(', ') : '5 项安全头齐全');
},
async notfound() {
const { res } = await j('/no/such/file-' + Date.now());
setRow('notfound', res.status === 404, `HTTP ${res.status}(期望 404)`);
}
};
async function runAll() { <footer class="footer">
const summary = document.getElementById('summary'); <div class="wrap">
summary.textContent = '测试运行中…'; <div class="footer-inner">
document.querySelectorAll('#tbl tr').forEach(tr => { <div class="footer-brand">
tr.children[1].textContent = '…'; tr.children[1].className = 'st pending'; <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="10.51" x2="15.42" y2="6.51"/><line x1="8.59" y1="13.49" x2="15.42" y2="17.49"/></svg>
tr.children[2].textContent = ''; <span>DNFS 树状笔记</span>
}); </div>
let pass = 0, fail = 0; <nav class="footer-nav" aria-label="页脚导航">
for (const [name, fn] of Object.entries(tests)) { <a href="dnfs.html">树状笔记</a>
try { await fn(); } <a href="allcolor.html">调色板</a>
catch (e) { setRow(name, false, '异常: ' + e.message); } <a href="games/">小游戏</a>
const st = document.querySelector(`tr[data-t="${name}"] .st`); <a href="LICENSE.html">用户使用条例</a>
if (st && st.classList.contains('ok')) pass++; else fail++; </nav>
} <p class="footer-icp">
summary.innerHTML = `完成:<span class="ok">${pass} 通过</span> / <span class="fail">${fail} 失败</span>(共 ${pass + fail})`; <a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">蜀ICP备2024107064号-2</a>
} </p>
</script> </div>
</div>
</footer>
<script src="js/token_manager.js"></script>
<script src="js/index.js"></script>
</body> </body>
</html> </html>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
-1
View File
@@ -1 +0,0 @@
hello fsys — 这是 fsys 静态文件服务的测试文件。
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
这是中文内容:用于验证 URL 解码(UTF-8 编码文件名)是否生效。
-227
View File
@@ -1,227 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="DNFS 树状笔记:以树状结构组织知识的笔记社区,支持 Markdown、链接卡片、图片与文件分享。">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fdf6ee">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1e1712">
<link rel="icon" href="favicon.ico">
<link rel="stylesheet" href="css/theme.css">
<link rel="stylesheet" href="css/index.css">
<title>DNFS 树状笔记 — 让知识自然成体系</title>
</head>
<body>
<header class="header">
<div class="wrap">
<div class="header-inner">
<h1 id="logo">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="10.51" x2="15.42" y2="6.51"/><line x1="8.59" y1="13.49" x2="15.42" y2="17.49"/></svg>
<span>DNFS 树状笔记</span>
</h1>
<nav class="right-group" aria-label="用户操作">
<img src="favicon.ico" alt="用户头像" id="avatar" style="display:none">
<b id="name" style="display:none"></b>
<button id="enterBtn" class="btn btn-primary" style="display:none">进入笔记</button>
<button id="logoutBtn" class="btn btn-ghost" style="display:none">退出</button>
<button id="loginBtn" class="btn btn-primary">登录</button>
<button id="registerBtn" class="btn btn-ghost">注册</button>
</nav>
</div>
</div>
</header>
<main id="main-page">
<!-- ===== Hero ===== -->
<section class="hero">
<div class="hero-bg" aria-hidden="true">
<svg class="hero-tree" viewBox="0 0 800 440" fill="none">
<defs>
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="currentColor" stop-opacity="0.18"/>
<stop offset="100%" stop-color="currentColor" stop-opacity="0"/>
</radialGradient>
</defs>
<circle cx="400" cy="210" r="180" fill="url(#glow)"/>
<g class="tree-branches" stroke="currentColor" stroke-linecap="round">
<path pathLength="100" d="M400 400 L400 295" stroke-width="2.5"/>
<path pathLength="100" d="M400 295 C 365 265, 310 240, 260 205" stroke-width="2"/>
<path pathLength="100" d="M400 295 C 435 265, 490 240, 540 205" stroke-width="2"/>
<path pathLength="100" d="M260 205 C 238 178, 210 152, 185 120" stroke-width="1.5"/>
<path pathLength="100" d="M260 205 C 278 178, 300 152, 318 120" stroke-width="1.5"/>
<path pathLength="100" d="M540 205 C 518 178, 496 152, 478 120" stroke-width="1.5"/>
<path pathLength="100" d="M540 205 C 562 178, 590 152, 615 120" stroke-width="1.5"/>
<path pathLength="100" d="M185 120 C 172 100, 160 82, 150 60" stroke-width="1"/>
<path pathLength="100" d="M318 120 C 328 100, 338 82, 345 60" stroke-width="1"/>
<path pathLength="100" d="M478 120 C 468 100, 458 82, 452 60" stroke-width="1"/>
<path pathLength="100" d="M615 120 C 628 100, 640 82, 650 60" stroke-width="1"/>
</g>
<g class="tree-nodes" fill="currentColor">
<circle cx="400" cy="400" r="7" opacity=".9"/>
<circle cx="400" cy="295" r="5.5"/>
<circle cx="260" cy="205" r="5"/>
<circle cx="540" cy="205" r="5"/>
<circle cx="185" cy="120" r="4"/>
<circle cx="318" cy="120" r="4"/>
<circle cx="478" cy="120" r="4"/>
<circle cx="615" cy="120" r="4"/>
<circle cx="150" cy="60" r="3" opacity=".65"/>
<circle cx="345" cy="60" r="3" opacity=".65"/>
<circle cx="452" cy="60" r="3" opacity=".65"/>
<circle cx="650" cy="60" r="3" opacity=".65"/>
</g>
</svg>
</div>
<div class="wrap hero-inner">
<p class="hero-badge"><span class="badge-dot"></span>树状结构 · Markdown · 多媒体分享</p>
<h2 class="hero-title">让知识像树一样<br><span class="accent-text">自然生长</span></h2>
<p class="hero-sub">每个节点都可以生长出子节点,像树枝一样层层延展<br>让零散的想法汇聚成体系</p>
<div class="hero-actions">
<button class="btn btn-primary btn-lg" data-cta>开始记录</button>
<a class="btn btn-ghost btn-lg" href="#features">了解更多</a>
</div>
</div>
<a class="scroll-hint" href="#features" aria-label="下滑了解特性">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</a>
</section>
<!-- ===== 核心特性 ===== -->
<section id="features" class="section features-section">
<div class="wrap">
<p class="section-eyebrow">核心特性</p>
<h2 class="section-title">为什么选择树状笔记</h2>
<p class="section-sub">从灵感到体系,让知识在树上自然生长</p>
<div class="features">
<div class="feature-card">
<div class="feature-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="10.51" x2="15.42" y2="6.51"/><line x1="8.59" y1="13.49" x2="15.42" y2="17.49"/></svg>
</div>
<div class="feature-body">
<h3>树状结构组织</h3>
<p>每个节点都能生长出子节点,像树枝般层层延展,让零散的想法自然汇聚成体系。</p>
</div>
<span class="feature-tag">无限层级</span>
</div>
<div class="feature-card">
<div class="feature-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
</div>
<div class="feature-body">
<h3>Markdown 写作</h3>
<p>原生支持 Markdown 渲染,代码块、引用、列表一应俱全,专注内容本身。</p>
</div>
<span class="feature-tag">专注写作</span>
</div>
<div class="feature-card">
<div class="feature-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
</div>
<div class="feature-body">
<h3>多媒体分享</h3>
<p>链接卡片、图片直显、文件下载,多种内容形态一站承载,分享更自由。</p>
</div>
<span class="feature-tag">自由分享</span>
</div>
<div class="feature-card">
<div class="feature-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
</div>
<div class="feature-body">
<h3>深色模式</h3>
<p>跟随系统偏好自动切换明暗主题,深夜记录也不刺眼,日夜皆宜。</p>
</div>
<span class="feature-tag">日夜皆宜</span>
</div>
</div>
</div>
</section>
<!-- ===== 关于本站 ===== -->
<section class="section about-section">
<div class="wrap">
<p class="section-eyebrow">我们的理念</p>
<h2 class="section-title">关于本站</h2>
<div class="about-panel">
<svg class="about-deco" viewBox="0 0 200 200" fill="none" aria-hidden="true">
<circle cx="100" cy="100" r="90" stroke="currentColor" stroke-width=".6" opacity=".15"/>
<circle cx="100" cy="100" r="60" stroke="currentColor" stroke-width=".5" opacity=".10"/>
<circle cx="100" cy="100" r="30" stroke="currentColor" stroke-width=".4" opacity=".08"/>
</svg>
<p class="site-intro">
DNFS 树状笔记是一个以树状结构组织内容的笔记社区:每个节点都可以生长出子节点,
像树枝一样层层延展,让知识自然成体系。支持 Markdown 文本、链接卡片、图片与文件分享,
注册登录后即可创建属于你自己的知识树。
</p>
<ul class="about-stats">
<li><b>∞</b><span>节点层级无上限</span></li>
<li><b>MD</b><span>原生 Markdown 渲染</span></li>
<li><b>4+</b><span>内容形态一站承载</span></li>
</ul>
</div>
<nav class="quick-links" aria-label="快捷链接">
<a href="allcolor.html">调色板</a>
<a href="games/">小游戏</a>
<a href="LICENSE.html">用户使用条例</a>
</nav>
</div>
</section>
<!-- ===== 结尾 CTA ===== -->
<section class="section cta-section" aria-label="立即开始">
<div class="wrap">
<div class="cta-band">
<svg class="cta-tree" viewBox="0 0 400 200" fill="none" aria-hidden="true">
<g stroke="currentColor" stroke-width="1.2" stroke-linecap="round" opacity=".6">
<path d="M200 190 L200 140"/>
<path d="M200 140 C 175 120, 145 105, 120 85"/>
<path d="M200 140 C 225 120, 255 105, 280 85"/>
<path d="M120 85 C 105 65, 90 50, 80 30"/>
<path d="M120 85 C 135 65, 148 50, 155 30"/>
<path d="M280 85 C 265 65, 252 50, 245 30"/>
<path d="M280 85 C 295 65, 310 50, 320 30"/>
</g>
<g fill="currentColor" opacity=".5">
<circle cx="200" cy="190" r="4"/>
<circle cx="200" cy="140" r="3"/>
<circle cx="120" cy="85" r="2.5"/>
<circle cx="280" cy="85" r="2.5"/>
<circle cx="80" cy="30" r="2"/>
<circle cx="155" cy="30" r="2"/>
<circle cx="245" cy="30" r="2"/>
<circle cx="320" cy="30" r="2"/>
</g>
</svg>
<h2 class="cta-title">准备好让知识生长了吗?</h2>
<p class="cta-sub">注册一个账号,种下你的第一棵知识树</p>
<button class="btn btn-cta btn-lg" data-cta>立即开始</button>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="wrap">
<div class="footer-inner">
<div class="footer-brand">
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="10.51" x2="15.42" y2="6.51"/><line x1="8.59" y1="13.49" x2="15.42" y2="17.49"/></svg>
<span>DNFS 树状笔记</span>
</div>
<nav class="footer-nav" aria-label="页脚导航">
<a href="dnfs.html">树状笔记</a>
<a href="allcolor.html">调色板</a>
<a href="games/">小游戏</a>
<a href="LICENSE.html">用户使用条例</a>
</nav>
<p class="footer-icp">
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">蜀ICP备2024107064号-2</a>
</p>
</div>
</div>
</footer>
<script src="js/token_manager.js"></script>
<script src="js/index.js"></script>
</body>
</html>