三条线合并入库 —— 均已完成并上线(源码与生产一致,此前只部署未入仓)。 ⚠️ 其中域名迁移线为**另一会话**产出,本会话只做入库、**未复验其正确性**(它自报零回归)。 【档案 134 · 注册页人机验证 + 邮箱验证码】 - DB 迁移 v10:users.email(唯一索引 LOWER(email))+ email_codes 事件表(2 索引) - 新增模块 src/web/{register-guard,mail,turnstile,email-code}.ts - routes/auth.ts:新增 GET /api/auth/register/config、POST /api/auth/register/email-code; 注册接口加人机验证与验证码校验;config.ts 新增 12 项配置(默认空 ⇒ 不配 = 老行为) - 邮件走**可插拔驱动**(brevo/http/log),发件人 [email protected](Brevo 域名已认证 + DKIM + SPF) - 防爆破:三层配额(邮箱 6/h、8/天;IP 20/h;全局 200/h)+ 递增冷却阶梯 (60→60→180→300→900→1800s)+ 试错 5 次作废 + 码只存哈希 + 单次使用 + 与用户名绑定 - Turnstile 服务端校 **success + action + hostname 三项**:sitekey 是公开的, 只校 success 时"拿我们的 sitekey 在自己站点替真人取合法 token 再打我们接口"这条路是通的 - 新增 test/register-guard.test.mjs(19 用例) 【档案 137 · 品牌标识改造 — 去 DeepSeek 图形】 - login/register/admin 页头:删 DeepSeek 鲸鱼图标 + 「DeepSeek」文字图形 → 平台标识(中文「能力枢纽」/英语及其他语言「CapabilityNet」,走 i18n 词条 brand.name) - portal 顶栏换图标(页面名「管理门户」保留) - 新建 web/favicon.svg(平台自有 hub 图标,避开 DeepSeek 蓝)+ 四页 favicon 指向它 - 新增 test/i18n-brand.test.mjs(node:vm 跑真实 i18n.js,六条语言路径断言渲染结果) - scripts/verify-static.mjs 新增 SVG 段:XML 注释不得含 ASCII 双连字符(否则整份 SVG 解析失败、图标静默不显示 —— 实际踩到过) - 🔴 会话页面(实例内官方 dsh 界面)的标识**按用户要求未动**(也受 R2 约束) 【档案 135/136 · 域名迁移线(另一会话产出)】 - 域名收敛为 ai1net.com;旧域 alotbuy.com 降级为 301 过渡装置 - src/net/relay/{addr-override,directory,rendezvous,switcher}.ts 种子与候选链更新; src/web/server.ts、src/worker/relay-tunnel.ts、scripts/verify-cluster-domain.mjs - 档案 136 = 控制面按两台中继取并集(**已立项、未落地**) 验证(本会话两条线):新增单测 21 条全通过|全量 221 pass / 0 fail / 1 skipped| verify-static 全合格|其余 10 个 verify 脚本全 OK|线上实测:Turnstile 假 token 403、 发码 delivered、四页 deepseek 命中 0、favicon 200。
273 lines
13 KiB
CSS
273 lines
13 KiB
CSS
/* design system — OS-desktop style */
|
||
:root {
|
||
--accent: #4d7cfe;
|
||
--accent-2: #38d6d0;
|
||
--ink: #0f1c33;
|
||
--ink-soft: #5a6b85;
|
||
--line: #e6ebf4;
|
||
--surface: #ffffff;
|
||
--surface-2: #f6f8fc;
|
||
--danger: #e5484d;
|
||
--ok: #1fb56a;
|
||
--warn: #f5a623;
|
||
--radius: 14px;
|
||
--shadow: 0 18px 50px -18px rgba(9, 24, 58, 0.35);
|
||
--shadow-sm: 0 6px 18px -8px rgba(9, 24, 58, 0.28);
|
||
--mono: "SFMono-Regular", "Cascadia Code", Consolas, monospace;
|
||
}
|
||
* { box-sizing: border-box; }
|
||
html, body { height: 100%; }
|
||
body {
|
||
margin: 0;
|
||
font-family: -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
|
||
color: var(--ink);
|
||
background: #0b1a33;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
/* ---------- auth pages ---------- */
|
||
.auth-bg {
|
||
min-height: 100vh;
|
||
display: grid;
|
||
place-items: center;
|
||
padding: 24px;
|
||
background:
|
||
radial-gradient(60rem 40rem at 80% -10%, rgba(56, 214, 208, 0.18), transparent 60%),
|
||
radial-gradient(50rem 36rem at 10% 110%, rgba(77, 124, 254, 0.28), transparent 60%),
|
||
linear-gradient(160deg, #0d1f3c, #0a1530);
|
||
}
|
||
.auth-card {
|
||
width: 100%;
|
||
max-width: 380px;
|
||
background: var(--surface);
|
||
border-radius: 20px;
|
||
padding: 32px 30px 26px;
|
||
box-shadow: var(--shadow);
|
||
}
|
||
.auth-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
|
||
.auth-brand .logo { width: 34px; height: 34px; border-radius: 9px; }
|
||
.auth-brand h1 { font-size: 19px; margin: 0; letter-spacing: 0.2px; }
|
||
.wordmark { display: inline-flex; align-items: center; }
|
||
.wordmark svg { height: 100%; width: auto; display: block; }
|
||
/* 2026-09-19:登录 / 注册页的品牌标识由 **DeepSeek 图形**改为**本平台文字**
|
||
(中文「能力枢纽」/ 英文及其他语言 CapabilityNet,走 i18n 词条 `brand.name`)。
|
||
上面那套定高 + svg 的规则对文字不适用 ⇒ 这里补字号 / 字重 / 行高(`height:auto` 覆盖旧定高)。
|
||
⚠️ `.topbar .wordmark`(admin / portal 顶栏)**未动** —— 那两处仍用 svg,用户本轮只要求登录 / 注册页。 */
|
||
.auth-brand .wordmark { height: auto; font-size: 19px; font-weight: 600; line-height: 1.2; letter-spacing: 0.2px; color: var(--ink); }
|
||
.topbar .wordmark { height: 20px; color: #dbe5ff; }
|
||
.auth-sub { color: var(--ink-soft); font-size: 13px; margin: 6px 0 22px; }
|
||
.field { margin-bottom: 14px; }
|
||
.field label { display: block; font-size: 13px; color: var(--ink-soft); margin-bottom: 6px; }
|
||
.field input {
|
||
width: 100%;
|
||
padding: 11px 13px;
|
||
font-size: 14px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 10px;
|
||
background: var(--surface-2);
|
||
color: var(--ink);
|
||
outline: none;
|
||
transition: border-color .15s, box-shadow .15s;
|
||
}
|
||
.field input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(77, 124, 254, 0.15); background: #fff; }
|
||
.btn {
|
||
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
||
border: 0; cursor: pointer; font-size: 14px; font-weight: 600;
|
||
padding: 11px 16px; border-radius: 10px; transition: transform .06s, filter .15s, background .15s;
|
||
}
|
||
.btn:active { transform: translateY(1px); }
|
||
.btn.primary { width: 100%; background: var(--accent); color: #fff; }
|
||
.btn.primary:hover { filter: brightness(1.06); }
|
||
.btn.ghost { background: var(--surface-2); color: var(--ink); border: 1px solid var(--line); }
|
||
.btn.ghost:hover { background: #eef1f8; }
|
||
.btn.danger { background: var(--danger); color: #fff; }
|
||
.btn.small { padding: 6px 10px; font-size: 13px; border-radius: 8px; }
|
||
.msg { min-height: 20px; font-size: 13px; margin-top: 10px; }
|
||
.msg.err { color: var(--danger); }
|
||
.msg.ok { color: var(--ok); }
|
||
.auth-alt { margin-top: 16px; text-align: center; font-size: 13px; color: var(--ink-soft); }
|
||
.auth-alt a { color: var(--accent); text-decoration: none; font-weight: 600; }
|
||
|
||
/* ---------- desktop ---------- */
|
||
.desktop { position: fixed; inset: 0; display: flex; flex-direction: column; }
|
||
.wallpaper {
|
||
position: absolute; inset: 0; z-index: 0;
|
||
background:
|
||
radial-gradient(70rem 40rem at 75% -20%, rgba(56, 214, 208, 0.20), transparent 55%),
|
||
radial-gradient(60rem 40rem at 10% 110%, rgba(77, 124, 254, 0.30), transparent 55%),
|
||
linear-gradient(160deg, #0e2246 0%, #0a1630 60%, #071021 100%);
|
||
}
|
||
.wallpaper::after {
|
||
content: ""; position: absolute; inset: 0;
|
||
background-image: radial-gradient(rgba(255,255,255,0.05) 1px, transparent 1px);
|
||
background-size: 26px 26px;
|
||
}
|
||
|
||
.topbar {
|
||
position: relative; z-index: 2;
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: 0 18px; height: 52px;
|
||
background: rgba(13, 26, 52, 0.55); backdrop-filter: blur(14px);
|
||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||
color: #dbe5ff;
|
||
}
|
||
.topbar .brand { display: flex; align-items: center; gap: 9px; font-weight: 600; }
|
||
.topbar .brand .logo { width: 24px; height: 24px; border-radius: 6px; }
|
||
.topbar .right { display: flex; align-items: center; gap: 12px; font-size: 13px; }
|
||
.topbar .who { font-weight: 600; }
|
||
.topbar .role {
|
||
font-size: 11px; padding: 2px 8px; border-radius: 999px;
|
||
background: rgba(77, 124, 254, 0.25); color: #bcd0ff; border: 1px solid rgba(77,124,254,0.4);
|
||
}
|
||
.topbar .logout { background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.14); color: #dbe5ff; }
|
||
.topbar .logout:hover { background: rgba(255,255,255,0.14); }
|
||
|
||
.icons { position: relative; z-index: 1; flex: 1; display: flex; flex-wrap: wrap; align-content: flex-start; gap: 10px; padding: 22px; }
|
||
.desk-icon {
|
||
width: 96px; display: flex; flex-direction: column; align-items: center; gap: 7px;
|
||
padding: 10px 6px; border-radius: 12px; cursor: pointer; user-select: none;
|
||
border: 1px solid transparent;
|
||
}
|
||
.desk-icon:hover { background: rgba(255,255,255,0.07); border-color: rgba(255,255,255,0.10); }
|
||
.desk-icon .tile {
|
||
width: 62px; height: 62px; border-radius: 16px; display: grid; place-items: center;
|
||
color: #fff; box-shadow: 0 10px 24px -10px rgba(0,0,0,0.5);
|
||
}
|
||
.desk-icon .tile svg { width: 30px; height: 30px; }
|
||
.desk-icon .label { color: #e6edff; font-size: 12.5px; text-align: center; text-shadow: 0 1px 3px rgba(0,0,0,0.5); }
|
||
.tile.dsh { background: linear-gradient(135deg, #4d7cfe, #38d6d0); }
|
||
.tile.files { background: linear-gradient(135deg, #f5b14d, #f58a4d); }
|
||
.tile.admin { background: linear-gradient(135deg, #9b6cff, #4d7cfe); }
|
||
|
||
.taskbar {
|
||
position: relative; z-index: 2; display: flex; align-items: center; justify-content: center; gap: 10px;
|
||
height: 56px; background: rgba(13, 26, 52, 0.6); backdrop-filter: blur(14px);
|
||
border-top: 1px solid rgba(255,255,255,0.08);
|
||
}
|
||
.taskbar .app {
|
||
width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center; cursor: pointer;
|
||
border: 1px solid transparent; transition: background .15s, transform .1s;
|
||
}
|
||
.taskbar .app:hover { background: rgba(255,255,255,0.1); }
|
||
.taskbar .app.active { background: rgba(255,255,255,0.14); border-color: rgba(255,255,255,0.18); }
|
||
.taskbar .app svg { width: 22px; height: 22px; }
|
||
.taskbar .clock { position: absolute; right: 18px; color: #cfe0ff; font-size: 13px; }
|
||
|
||
/* ---------- windows ---------- */
|
||
.window {
|
||
position: absolute; z-index: 10; min-width: 380px; min-height: 220px;
|
||
background: var(--surface); border-radius: 16px; box-shadow: var(--shadow);
|
||
display: flex; flex-direction: column; overflow: hidden;
|
||
animation: win-in .18s ease;
|
||
}
|
||
@keyframes win-in { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
|
||
.window.focused { box-shadow: 0 26px 70px -20px rgba(0,0,0,0.55); }
|
||
.window .rz, .window .resize { position: absolute; z-index: 3; touch-action: none; }
|
||
.window .rz-n { top: 0; left: 16px; right: 16px; height: 6px; cursor: ns-resize; }
|
||
.window .rz-s { bottom: 0; left: 16px; right: 16px; height: 6px; cursor: ns-resize; }
|
||
.window .rz-e { right: 0; top: 48px; bottom: 0; width: 6px; cursor: ew-resize; }
|
||
.window .rz-w { left: 0; top: 48px; bottom: 0; width: 6px; cursor: ew-resize; }
|
||
.window .rz-ne { top: 30px; right: 0; width: 14px; height: 14px; cursor: nesw-resize; }
|
||
.window .rz-nw { top: 30px; left: 0; width: 14px; height: 14px; cursor: nwse-resize; }
|
||
.window .rz-se, .window .resize { bottom: 0; right: 0; width: 16px; height: 16px; cursor: nwse-resize; }
|
||
.window .rz-sw { bottom: 0; left: 0; width: 14px; height: 14px; cursor: nesw-resize; }
|
||
.window .rz-se::after, .window .resize::after {
|
||
content: ""; position: absolute; right: 4px; bottom: 4px; width: 9px; height: 9px;
|
||
border-right: 2px solid rgba(120, 132, 160, 0.55);
|
||
border-bottom: 2px solid rgba(120, 132, 160, 0.55);
|
||
border-bottom-right-radius: 4px;
|
||
}
|
||
.window-title {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: 0 14px; height: 44px; background: var(--surface-2); border-bottom: 1px solid var(--line);
|
||
cursor: grab; user-select: none;
|
||
}
|
||
.window-title .t { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 14px; }
|
||
.window-title .t svg { width: 16px; height: 16px; color: var(--accent); }
|
||
.window-title .controls { display: flex; gap: 6px; }
|
||
.window-title .controls button {
|
||
width: 26px; height: 26px; border: 0; border-radius: 7px; background: transparent; cursor: pointer;
|
||
color: var(--ink-soft); font-size: 13px; line-height: 1; display: grid; place-items: center;
|
||
}
|
||
.window-title .controls button:hover { background: #e8ecf5; }
|
||
.window-title .controls button.close:hover { background: var(--danger); color: #fff; }
|
||
.window-body { flex: 1; overflow: auto; padding: 16px; }
|
||
|
||
/* file table */
|
||
.pathbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
|
||
.pathbar .crumbs { display: flex; align-items: center; gap: 4px; font-size: 13px; color: var(--accent); }
|
||
.pathbar .crumbs .sep { color: var(--ink-soft); }
|
||
.pathbar .crumb { cursor: pointer; }
|
||
.pathbar .crumb:hover { text-decoration: underline; }
|
||
table.files { width: 100%; border-collapse: collapse; font-size: 13.5px; }
|
||
table.files th, table.files td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); }
|
||
table.files th { color: var(--ink-soft); font-weight: 600; font-size: 12px; }
|
||
table.files tbody tr:hover { background: var(--surface-2); }
|
||
table.files .dir { color: var(--accent); cursor: pointer; font-weight: 600; }
|
||
table.files .dir:hover { text-decoration: underline; }
|
||
|
||
/* dsh status */
|
||
.stat { display: flex; align-items: center; gap: 8px; font-size: 14px; padding: 10px 12px; border-radius: 10px; background: var(--surface-2); margin-bottom: 12px; }
|
||
.dot { width: 9px; height: 9px; border-radius: 50%; }
|
||
.dot.on { background: var(--ok); box-shadow: 0 0 0 4px rgba(31,181,106,0.15); }
|
||
.dot.off { background: var(--ink-soft); }
|
||
.row-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
|
||
.section-label { font-size: 12px; color: var(--ink-soft); font-weight: 600; margin: 16px 0 8px; }
|
||
.plugin-item { display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 8px; font-size: 13.5px; }
|
||
.plugin-item:hover { background: var(--surface-2); }
|
||
|
||
/* admin */
|
||
table.admin { width: 100%; border-collapse: collapse; font-size: 13.5px; }
|
||
table.admin th, table.admin td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); }
|
||
table.admin th { color: var(--ink-soft); font-weight: 600; font-size: 12px; }
|
||
.badge { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 600; }
|
||
.badge.admin { background: #e7edff; color: #3f5fd0; }
|
||
.badge.pending { background: #fff3dd; color: #b0761a; }
|
||
.badge.active { background: #e4f7ec; color: #15995a; }
|
||
.badge.disabled { background: #eceff4; color: #6a7588; }
|
||
|
||
.hidden { display: none !important; }
|
||
|
||
/* ---------- mobile ---------- */
|
||
@media (max-width: 768px) {
|
||
.auth-card { max-width: 100%; overflow-x: auto; }
|
||
.desktop { overflow: hidden; }
|
||
.topbar { padding: 0 10px; height: 48px; }
|
||
.topbar .role { display: none; }
|
||
.icons { padding: 10px; gap: 6px; }
|
||
.desk-icon { width: 80px; padding: 8px 4px; }
|
||
.desk-icon .tile { width: 52px; height: 52px; border-radius: 14px; }
|
||
.desk-icon .label { font-size: 11px; }
|
||
.desk-path { padding: 8px 10px 0; font-size: 12px; }
|
||
.desk-path .loc { max-width: 70vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.taskbar { height: 50px; }
|
||
.taskbar .clock { display: none; }
|
||
/* windows go full-screen on phones */
|
||
.window {
|
||
position: fixed !important;
|
||
inset: 0 !important;
|
||
left: 0 !important; top: 0 !important;
|
||
width: 100% !important; height: 100% !important;
|
||
min-width: 0 !important; min-height: 0 !important;
|
||
max-width: none !important;
|
||
border-radius: 0 !important;
|
||
}
|
||
.window .rz, .window .resize { display: none; }
|
||
.window-body { -webkit-overflow-scrolling: touch; }
|
||
.window-body table { min-width: 520px; }
|
||
.window-title { height: 48px; }
|
||
.window-title .controls button { width: 34px; height: 34px; }
|
||
}
|
||
|
||
/* desktop file grid + path bar */
|
||
.desk-path { position: relative; z-index: 1; display: flex; align-items: center; gap: 10px; padding: 12px 22px 0; color: #cfe0ff; font-size: 13px; }
|
||
.desk-path .back { background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.16); color: #dbe5ff; }
|
||
.desk-path .back:hover { background: rgba(255,255,255,0.14); }
|
||
.desk-path .loc { opacity: .85; }
|
||
.tile { overflow: hidden; }
|
||
.tile img { min-width: 0; min-height: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; display: block; }
|
||
.tile.file { background: linear-gradient(135deg, #7f9cf5, #5a74d0); }
|
||
.desk-divider { width: 100%; height: 0; border-top: 1px solid rgba(255,255,255,0.14); margin: 4px 2px 10px; }
|
||
#fileIcons { display: contents; }
|
||
#fileIcons .desk-icon .label { font-size: 12px; max-width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|