init: init proj

This commit is contained in:
2026-08-25 11:35:05 +08:00
commit bf7f2df67e
80 changed files with 9936 additions and 0 deletions

135
docs/ADDING-REGIONS.md Normal file
View File

@@ -0,0 +1,135 @@
# 新增地区与新增站点
当舰队里加入新地区(香港、日本、韩国、台湾、英国……)的节点时,按本文操作。
> 背景:策略组的地区筛选靠**节点名称正则**,不靠节点的 `region` 字段。所以节点命名是一切的前提。
## 当前状态2026-08-24
只有美国与新加坡两个地区的节点,因此策略组里只有 `🇺🇸 United States``🇸🇬 Singapore` 两个地区组。
香港、日本、台湾、英国、韩国的地区组**已删除**——留着会是空组,在客户端里显示为无可用节点。
兜底的 `🌍 Other` 组也已删除:所有节点都能被现有两个地区组收纳,它恒为空。
## 一、给新节点命名(决定它能被哪个组收纳)
命名格式:`<国旗> <国家码> · <站点码> · <协议> · <出口>`
```
🇭🇰 HK · VMess · 直连
🇯🇵 JP · Trojan · WARP
🇺🇸 US · LA · VMess · 直连 ← 同国多站点时插入站点码
```
- **国家码必须出现在名字里**HKJPUSSG…地区组的正则靠它匹配
- 出口词(`直连` / `WARP`)必须保留:客户端里没有出口标签,用户只能靠名字区分同协议的两条线路。界面上显示时会自动隐去,不必担心重复
- 站点码LARNAWS用于节点页的分段归类来自节点的 `region` 字段
节点的 `region` 字段填站点码(`SG``LA``RN``HK`…),它决定节点页里归到哪一段。
## 二、让站点标题显示国旗
`web/src/components/stations.ts``STATION_LABELS` 加一行:
```ts
const STATION_LABELS: Record<string, string> = {
SG: '🇸🇬 SG AWS',
LA: '🇺🇸 US LA',
RN: '🇺🇸 US RN',
HK: '🇭🇰 HK 香港站点名', // ← 新增
};
```
未配置的站点码会原样显示,不影响功能。
## 三、新建地区策略组
在「策略组」页点「添加策略组」,按下表填写:
| 字段 | 值 |
|---|---|
| 名称 | `🇭🇰 Hong Kong`emoji 前缀 + 英文原名) |
| 类型 | `url-test` |
| 成员 | 留空 |
| 收纳全部节点 | **开启** |
| 名称筛选正则 | 见下方对照表 |
| 测速 URL | `http://cp.cloudflare.com/generate_204` |
| 间隔 | `300` |
| 容差 | `50` |
「收纳全部节点」+「筛选正则」的组合会生成 `include-all-proxies=true, policy-regex-filter=…`Surge`include-all-proxies: true, filter: …`Clash`use=true, policy-regex-filter=…`ShadowRocket三端都是**动态筛选**——之后再加同地区节点会自动进组,不用改配置。
各地区的正则(取自原模板,可直接复制):
```
香港 港|🇭🇰|香港|HK|Hong
日本 日|🇯🇵|日本|JP|Japan
台湾 台|🇹🇼|台湾|TW|Tai
韩国 韩|韩国|Korea|KR|🇰🇷
英国 🇬🇧|英国|英|英格兰|UK|United Kingdom
新加坡 坡|🇸🇬|新加坡|狮城|SG|Singapore
美国 美|🇺🇸|美国|US|States|American
```
## 四、兜底组 `🌍 Other`(当前不存在)
原模板有一个 `🌍 Other` 组,用否定前瞻正则收纳「不属于任何已建地区组」的节点。**当前已删除**,因为现有节点都能被美/新两个组收纳,它恒为空。
只有当你加入的节点**不属于任何已建地区组**(比如加了德国节点但没建德国组)时,才需要重建它:
| 字段 | 值 |
|---|---|
| 名称 | `🌍 Other` |
| 类型 | `url-test`,收纳全部节点开启 |
| 筛选正则 | 见下(把所有**已建**地区组的关键字排除掉) |
```
^((?!港|🇭🇰|香港|HK|Hong|日|🇯🇵|日本|JP|Japan|坡|🇸🇬|新加坡|狮城|SG|Singapore|美|🇺🇸|美国|US|States|American|Remain|Expired|官网|如需|套餐|去除|剩余|距离|Reset|重置|流量).)+$
```
**如果重建了 Other每次新增地区组都必须往这个否定前瞻里追加对应关键字**,否则新地区节点会同时出现在自己的组和 Other 里。
## 五、把新地区加进服务组的可选项
新建地区组后,它不会自动出现在服务组的成员里。按需编辑这些组,把新地区加进成员列表:
| 组 | 当前成员 | 建议 |
|---|---|---|
| `⚡️ Automatic` | SG、US | 加入所有地区组 |
| `🚀 Proxy` | Automatic、SG、US | 加入所有地区组 |
| `🎬 Streaming` | SG、US | 加入解锁能力强的地区HK/TW/JP |
| `🤖 AI` | Automatic、US、SG | 加入 JP、US 等 AI 服务可用地区 |
| `🌐 Global Services` | Automatic、US、SG | 同 AI |
| `🍎 Apple` | Mainland、US | 按需加入 HK |
成员的顺序就是客户端里的排列顺序,**第一个成员是该组的默认选中项**。
## 六、地区流媒体解锁规则(可选)
模板原有 `stream_hk``stream_jp``stream_tw``stream_kr``stream_eu` 等按地区解锁的规则集。删除地区组时,这些规则被改指向了 `🎬 Streaming`
如果新建了对应地区组,可以在「规则」页把这些规则的策略改回该地区组,解锁效果更精准:
| 规则集 | 建议策略 |
|---|---|
| `stream_hk`non_ip + ip 两条) | `🇭🇰 Hong Kong` |
| `stream_jp`(两条) | `🇯🇵 Japan` |
| `stream_tw``stream_kr``stream_eu` | 对应地区组 |
## 七、验证
1. 「策略组」页:新地区组的**命中**一栏应列出该地区的所有节点
2. 「节点」页:新站点应有独立分段,标题显示国旗
3. 命令行校验 Clash 产物:
```bash
curl -s "http://<host>/sub/<token>/clash" > /tmp/c.yaml && mihomo -t -f /tmp/c.yaml
```
4. 若重建过 `🌍 Other`,确认它的命中里**不再包含**新地区节点
## 删除某个地区(反向操作)
1. 把指向该组的规则改指向别的组否则规则会被自动跳过——IR 层有悬空引用防护,但那是兜底不是意图)
2. 从其他组的成员里摘掉它
3. 删除该组
4. Other 的排除列表可以保留该地区关键字(留着无害,将来重建时省事)
> 上述 13 步如果漏做生成器不会产出无效配置悬空引用会被自动跳过、FINAL 会降级为 DIRECT但分流意图会悄悄改变所以要显式处理。

33
docs/SEEDING.md Normal file
View File

@@ -0,0 +1,33 @@
# 节点导入指引
proxy-station 不直接读取 edge-nodes 工作区(那里全是真实凭据,保持解耦),节点通过 Web 界面批量粘贴分享链接导入。
## 从 edge-nodes 交付文档导入
每个节点目录 `<id>-edge-node-docs/` 已生成好分享链接:
- `<proto>-<egress>.link`——VMessTrojan 的分享链接文件(`vmess://``trojan://`
- Shadowsocks 线路没有通用分享链接,用 sing-box JSON`ss-*-singbox.json`)里的参数手动添加,或按 SIP002 + v2ray-plugin 格式自拼:
`ss://base64url(chacha20-poly1305:密码)@域名:443?plugin=v2ray-plugin%3Btls%3Bmode%3Dwebsocket%3Bhost%3D域名%3Bpath%3D%2F路径#名称`
- Hysteria2 线路在 `scripts/node-links/` 下有 `hysteria2://` 链接(含 `obfs=salamander&obfs-password=…`
操作:
1. 在 edge-nodes 侧汇总链接:`cat ~/Development/Fusion/Projects/edge-nodes/*-edge-node-docs/*.link`
2. 打开 proxy-station「节点」页 →「批量导入」→ 粘贴(每行一条)→「解析预览」确认字段 →「导入」
3. SOCKS5 线路为内部使用,不要导入本系统
## 命名建议(影响地区组归类)
模板的地区策略组按**节点名正则**筛选(如 `坡|🇸🇬|新加坡|狮城|SG|Singapore``美|🇺🇸|美国|US`)。命名时带上地区词与出口词,例如:
- `SG-vmess-direct``SG-trojan-warp`(命中 Singapore 组)
- `US-LA-trojan-direct``US-RN-vmess-warp`(命中 United States 组;只写 LARN 不会命中任何地区组,会落入 Other
名称含 `direct``warp` 会自动打出口标签,线路矩阵按此归位(琥珀 = 直连,青 = WARP
## 导入后核对
- 「节点」页线路矩阵:每个站点的协议 × 出口格子是否齐全
- 「策略组」页:地区组的「命中」行是否圈中了预期节点
- 「订阅」页预览Surge 端确认 SS over WS 节点已被自动屏蔽预期行为Surge 不支持该组合)

720
docs/design-demos.html Normal file
View File

@@ -0,0 +1,720 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Proxy Station · 设计方向 Demo</title>
<style>
/* ============ 全局与切换器 ============ */
* { margin: 0; padding: 0; box-sizing: border-box; }
html { -webkit-font-smoothing: antialiased; }
body { font-family: -apple-system, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.switcher {
position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
display: flex; gap: 2px; z-index: 99;
background: rgba(20,20,20,0.92); backdrop-filter: blur(12px);
border-radius: 8px; padding: 3px;
box-shadow: 0 4px 24px rgba(0,0,0,0.25);
}
.switcher button {
appearance: none; border: none; cursor: pointer;
background: transparent; color: #999;
font: 500 13px/1 -apple-system, 'PingFang SC', sans-serif;
padding: 8px 14px; border-radius: 6px;
}
.switcher button:hover { color: #fff; }
.switcher button.on { background: #fff; color: #111; }
.demo { display: none; min-height: 100vh; padding: 88px 0 80px; }
.demo.on { display: block; }
.frame { max-width: 1080px; margin: 0 auto; padding: 0 40px; }
.demo-intro { margin-bottom: 36px; }
.demo-intro .no { font-size: 12px; letter-spacing: 0.12em; }
.demo-intro h1 { margin: 6px 0 8px; }
.demo-intro p { max-width: 640px; }
/* =========================================================
方向 A 「站牌导视」 —— 交通导视系统:白底、粗黑标题、
线路色圆标,站名牌是签名元素。名字都叫 station 了。
========================================================= */
.demo-a {
--bg: #f7f7f4; --ink: #17181a; --sub: #6e7073; --line: #e2e2dd;
--direct: #b26a00; --direct-bg: #faf1e2; --warp: #007a72; --warp-bg: #e2f2f0;
background: var(--bg); color: var(--ink);
}
.demo-a .demo-intro .no { color: var(--sub); }
.demo-a h1 { font-size: 30px; font-weight: 800; letter-spacing: -0.01em; }
.demo-a .demo-intro p { color: var(--sub); font-size: 14px; line-height: 1.8; }
.demo-a .toolbar { display: flex; gap: 10px; margin-bottom: 28px; }
.demo-a .btn {
appearance: none; cursor: pointer; font: 600 13px/1 inherit;
padding: 10px 18px; border-radius: 999px; border: 1.5px solid transparent;
transition: transform 0.06s;
}
.demo-a .btn:active { transform: scale(0.98); }
.demo-a .btn-primary { background: var(--ink); color: #fff; }
.demo-a .btn-primary:hover { background: #000; }
.demo-a .btn-secondary { background: transparent; border-color: var(--ink); color: var(--ink); }
.demo-a .btn-secondary:hover { background: rgba(0,0,0,0.05); }
.demo-a .btn-plain { background: transparent; color: var(--sub); padding-left: 8px; padding-right: 8px; }
.demo-a .btn-plain:hover { color: var(--ink); }
/* 站名牌:黑底白字 + 线路色条,仿轨道交通站名标 */
.demo-a .station-sign {
background: var(--ink); color: #fff; border-radius: 10px;
padding: 18px 22px 14px; margin-bottom: 2px;
display: flex; align-items: baseline; gap: 14px;
}
.demo-a .station-sign .code { font-size: 30px; font-weight: 800; letter-spacing: 0.02em; }
.demo-a .station-sign .name { font-size: 13px; color: #c9c9c4; font-family: ui-monospace, 'SF Mono', Menlo, monospace; }
.demo-a .station-sign .count { margin-left: auto; font-size: 12px; color: #8b8d90; }
.demo-a .sign-strip { display: flex; height: 5px; border-radius: 0 0 6px 6px; overflow: hidden; margin-bottom: 16px; }
.demo-a .sign-strip i { flex: 1; }
.demo-a .sign-strip .s-direct { background: var(--direct); }
.demo-a .sign-strip .s-warp { background: var(--warp); }
.demo-a .card {
background: #fff; border: 1px solid var(--line); border-radius: 10px;
padding: 22px; margin-bottom: 28px;
}
.demo-a .card .platforms { display: flex; gap: 28px; }
.demo-a .platform { flex: 1; }
.demo-a .platform-head {
font-size: 12px; font-weight: 700; letter-spacing: 0.1em;
padding-bottom: 8px; margin-bottom: 10px; border-bottom: 2.5px solid;
}
.demo-a .platform-direct .platform-head { color: var(--direct); border-color: var(--direct); }
.demo-a .platform-warp .platform-head { color: var(--warp); border-color: var(--warp); }
.demo-a .platform ul { list-style: none; }
.demo-a .platform li {
display: flex; align-items: center; gap: 10px;
padding: 7px 0; font-size: 13.5px;
}
.demo-a .proto-dot {
width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0;
display: inline-flex; align-items: center; justify-content: center;
font: 700 11px/1 inherit; color: #fff;
}
.demo-a .p-v { background: #275ad1; }
.demo-a .p-t { background: #8348c7; }
.demo-a .p-s { background: #2f7d3b; }
.demo-a .p-h { background: #c23d5d; }
.demo-a .platform .path { margin-left: auto; font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12px; color: var(--sub); }
.demo-a table { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.demo-a th {
text-align: left; font-size: 12px; font-weight: 700; letter-spacing: 0.06em;
color: var(--sub); padding: 12px 16px; border-bottom: 2px solid var(--ink); background: #fff;
}
.demo-a td { padding: 13px 16px; font-size: 13.5px; border-bottom: 1px solid var(--line); }
.demo-a tr:last-child td { border-bottom: none; }
.demo-a tr:hover td { background: #fbfbf8; }
.demo-a .mono { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12.5px; }
.demo-a .tag {
display: inline-flex; align-items: center; gap: 6px;
font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 999px;
}
.demo-a .tag i { width: 7px; height: 7px; border-radius: 50%; }
.demo-a .tag-direct { background: var(--direct-bg); color: var(--direct); }
.demo-a .tag-direct i { background: var(--direct); }
.demo-a .tag-warp { background: var(--warp-bg); color: var(--warp); }
.demo-a .tag-warp i { background: var(--warp); }
.demo-a .tag-off { background: #eeeeea; color: var(--sub); }
.demo-a .rowact { color: var(--sub); font-size: 12.5px; cursor: pointer; }
.demo-a .rowact:hover { color: var(--ink); text-decoration: underline; }
/* =========================================================
方向 B 「纸上台账」 —— 档案登记簿:纸面、衬线标题、
墨蓝正文、出口语义做成印章。
========================================================= */
.demo-b {
--paper: #f4f1e9; --card: #fbf9f3; --ink: #2b2a24; --navy: #23406e;
--sub: #8a8577; --line: #ddd7c8; --seal-red: #b53a2e; --seal-blue: #23406e;
background: var(--paper); color: var(--ink);
}
.demo-b .demo-intro .no { color: var(--sub); font-family: ui-monospace, 'SF Mono', Menlo, monospace; }
.demo-b h1 {
font-family: 'Songti SC', 'STSong', Georgia, serif;
font-size: 32px; font-weight: 700; color: var(--navy);
}
.demo-b .demo-intro p { color: var(--sub); font-size: 14px; line-height: 2; }
.demo-b .toolbar { display: flex; gap: 10px; margin-bottom: 28px; }
.demo-b .btn {
appearance: none; cursor: pointer;
font: 600 13px/1 'Songti SC', 'STSong', Georgia, serif;
padding: 10px 20px; border-radius: 3px; letter-spacing: 0.08em;
border: 1px solid var(--ink); background: transparent; color: var(--ink);
}
.demo-b .btn-primary { background: var(--navy); border-color: var(--navy); color: var(--paper); }
.demo-b .btn-primary:hover { background: #1a3157; }
.demo-b .btn:not(.btn-primary):hover { background: rgba(43,42,36,0.06); }
.demo-b .btn-plain { border-color: transparent; color: var(--sub); }
.demo-b .btn-plain:hover { color: var(--ink); background: transparent; text-decoration: underline; text-underline-offset: 4px; }
.demo-b .card {
background: var(--card); border: 1px solid var(--line); border-radius: 4px;
padding: 24px 26px; margin-bottom: 28px; position: relative;
}
.demo-b .card::before {
content: ''; position: absolute; inset: 6px; pointer-events: none;
border: 1px solid var(--line); border-radius: 2px;
}
.demo-b .card-head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 16px; position: relative; }
.demo-b .card-head .code {
font-family: 'Songti SC', 'STSong', Georgia, serif;
font-size: 26px; font-weight: 700; color: var(--navy);
}
.demo-b .card-head .domain { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12px; color: var(--sub); }
.demo-b .card-head .no { margin-left: auto; font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 11px; color: var(--sub); letter-spacing: 0.1em; }
/* 印章:出口语义 */
.demo-b .seal {
display: inline-flex; align-items: center; justify-content: center;
width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
border: 2px solid; transform: rotate(-6deg);
font: 700 13px/1 'Songti SC', 'STSong', Georgia, serif;
}
.demo-b .seal-direct { color: var(--seal-red); border-color: var(--seal-red); }
.demo-b .seal-warp { color: var(--seal-blue); border-color: var(--seal-blue); }
.demo-b .ledger { list-style: none; }
.demo-b .ledger li {
display: flex; align-items: center; gap: 14px;
padding: 10px 2px; border-bottom: 1px dotted var(--line); font-size: 14px;
}
.demo-b .ledger li:last-child { border-bottom: none; }
.demo-b .ledger .idx { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 11px; color: var(--sub); width: 26px; }
.demo-b .ledger .leader { flex: 1; border-bottom: 1px dotted var(--line); height: 1px; margin: 0 4px; }
.demo-b .ledger .path { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12.5px; color: var(--navy); }
.demo-b table { width: 100%; border-collapse: collapse; }
.demo-b th {
text-align: left; font-family: 'Songti SC', 'STSong', Georgia, serif;
font-size: 13px; font-weight: 700; color: var(--navy);
padding: 10px 14px; border-bottom: 3px double var(--ink);
}
.demo-b td { padding: 12px 14px; font-size: 13.5px; border-bottom: 1px solid var(--line); background: var(--card); }
.demo-b tr:hover td { background: #f6f3ea; }
.demo-b .mono { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12.5px; }
.demo-b .tag {
display: inline-block; font-size: 12px; padding: 2px 10px;
border: 1px solid; border-radius: 2px; letter-spacing: 0.06em;
}
.demo-b .tag-direct { color: var(--seal-red); border-color: var(--seal-red); }
.demo-b .tag-warp { color: var(--seal-blue); border-color: var(--seal-blue); }
.demo-b .tag-proto { color: var(--sub); border-color: var(--line); background: rgba(255,255,255,0.5); }
.demo-b .rowact { color: var(--navy); font-size: 12.5px; cursor: pointer; }
.demo-b .rowact:hover { text-decoration: underline; text-underline-offset: 3px; }
/* =========================================================
方向 C 「克制暗色」 —— 修正现有暗色的 AI 味:中性炭黑、
零装饰、纯排版分层,强调色只落在数据本身。
========================================================= */
.demo-c {
--bg: #161616; --panel: #1c1c1c; --line: #282828;
--text: #e8e6e1; --sub: #8f8c85;
--direct: #d0a05c; --warp: #5fb3a1;
background: var(--bg); color: var(--text);
}
.demo-c .demo-intro .no { color: var(--sub); }
.demo-c h1 { font-size: 26px; font-weight: 600; letter-spacing: -0.01em; }
.demo-c .demo-intro p { color: var(--sub); font-size: 14px; line-height: 1.9; }
.demo-c .toolbar { display: flex; gap: 8px; margin-bottom: 32px; }
.demo-c .btn {
appearance: none; cursor: pointer; font: 500 13px/1 inherit;
padding: 9px 16px; border-radius: 6px; border: 1px solid transparent;
background: transparent; color: var(--text);
}
.demo-c .btn-primary { background: var(--text); color: #111; }
.demo-c .btn-primary:hover { background: #fff; }
.demo-c .btn-secondary { border-color: var(--line); background: transparent; color: var(--text); }
.demo-c .btn-secondary:hover { border-color: #3a3a3a; background: var(--panel); }
.demo-c .btn-plain { color: var(--sub); }
.demo-c .btn-plain:hover { color: var(--text); }
.demo-c .card { border: 1px solid var(--line); border-radius: 8px; margin-bottom: 32px; overflow: hidden; }
.demo-c .card-head {
display: flex; align-items: baseline; gap: 12px;
padding: 16px 20px; border-bottom: 1px solid var(--line);
}
.demo-c .card-head .code { font-size: 15px; font-weight: 600; }
.demo-c .card-head .domain { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12px; color: var(--sub); }
.demo-c .card-head .count { margin-left: auto; font-size: 12px; color: var(--sub); font-variant-numeric: tabular-nums; }
.demo-c .lines { list-style: none; }
.demo-c .lines li {
display: grid; grid-template-columns: 110px 1fr 90px; align-items: center;
padding: 11px 20px; font-size: 13px; border-bottom: 1px solid var(--line);
}
.demo-c .lines li:last-child { border-bottom: none; }
.demo-c .lines li:hover { background: var(--panel); }
.demo-c .lines .proto { color: var(--text); font-weight: 500; }
.demo-c .lines .path { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12.5px; }
.demo-c .path-direct { color: var(--direct); }
.demo-c .path-warp { color: var(--warp); }
.demo-c .lines .eg { text-align: right; font-size: 12px; color: var(--sub); }
.demo-c table { width: 100%; border-collapse: collapse; }
.demo-c th {
text-align: left; font-size: 11.5px; font-weight: 500; letter-spacing: 0.08em;
text-transform: uppercase; color: var(--sub);
padding: 10px 14px; border-bottom: 1px solid var(--line);
}
.demo-c td { padding: 13px 14px; font-size: 13px; border-bottom: 1px solid var(--line); font-variant-numeric: tabular-nums; }
.demo-c tr:hover td { background: var(--panel); }
.demo-c .mono { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12.5px; }
.demo-c .tag { display: inline-block; font-size: 12px; padding: 2px 8px; border-radius: 4px; border: 1px solid var(--line); color: var(--sub); }
.demo-c .tag-direct { color: var(--direct); border-color: rgba(208,160,92,0.35); }
.demo-c .tag-warp { color: var(--warp); border-color: rgba(95,179,161,0.35); }
.demo-c .rowact { color: var(--sub); font-size: 12.5px; cursor: pointer; }
.demo-c .rowact:hover { color: var(--text); }
/* =========================================================
方向 D 「仪表工业」 —— Rams 式仪器面板:冷灰、指示灯、
实体按键手感、小写字母间距标签。
========================================================= */
.demo-d {
--bg: #e9eaec; --card: #f7f8f9; --ink: #1b1d1f; --sub: #70757b;
--line: #cfd2d6; --direct: #d9480f; --warp: #2b8a3e;
background: var(--bg); color: var(--ink);
}
.demo-d .demo-intro .no { color: var(--sub); letter-spacing: 0.18em; }
.demo-d h1 { font-size: 26px; font-weight: 700; letter-spacing: -0.01em; }
.demo-d .demo-intro p { color: var(--sub); font-size: 14px; line-height: 1.9; }
.demo-d .toolbar { display: flex; gap: 10px; margin-bottom: 28px; }
.demo-d .btn {
appearance: none; cursor: pointer; font: 600 13px/1 inherit;
padding: 9px 16px; border-radius: 4px;
background: var(--card); color: var(--ink);
border: 1px solid var(--line); border-bottom: 2.5px solid #b7bbc0;
}
.demo-d .btn:active { border-bottom-width: 1px; transform: translateY(1.5px); }
.demo-d .btn-primary { background: var(--ink); color: #fff; border-color: var(--ink); border-bottom-color: #000; }
.demo-d .btn-plain { background: transparent; border-color: transparent; color: var(--sub); }
.demo-d .btn-plain:hover { color: var(--ink); }
.demo-d .label {
font-size: 10.5px; font-weight: 600; letter-spacing: 0.16em;
text-transform: uppercase; color: var(--sub);
}
.demo-d .card {
background: var(--card); border: 1px solid var(--line); border-radius: 6px;
padding: 20px 22px; margin-bottom: 28px;
box-shadow: 0 1px 0 rgba(255,255,255,0.8) inset, 0 1px 3px rgba(27,29,31,0.06);
}
.demo-d .card-head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.demo-d .card-head .code { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.demo-d .card-head .domain { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12px; color: var(--sub); }
.demo-d .meter { margin-left: auto; display: flex; align-items: center; gap: 6px; }
.demo-d .lamp { width: 9px; height: 9px; border-radius: 50%; }
.demo-d .lamp-direct { background: var(--direct); box-shadow: 0 0 0 2.5px rgba(217,72,15,0.15); }
.demo-d .lamp-warp { background: var(--warp); box-shadow: 0 0 0 2.5px rgba(43,138,62,0.15); }
.demo-d .grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.demo-d .slot {
border: 1px solid var(--line); border-radius: 4px; background: #fff;
padding: 10px 12px; display: flex; align-items: center; gap: 10px; font-size: 13px;
}
.demo-d .slot .path { margin-left: auto; font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12px; color: var(--sub); }
.demo-d table { width: 100%; border-collapse: collapse; background: var(--card); border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
.demo-d th { text-align: left; padding: 11px 14px; border-bottom: 1px solid var(--line); background: #eff1f3; }
.demo-d td { padding: 12px 14px; font-size: 13px; border-bottom: 1px solid #e3e5e8; font-variant-numeric: tabular-nums; }
.demo-d tr:last-child td { border-bottom: none; }
.demo-d tr:hover td { background: #fbfcfd; }
.demo-d .mono { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12.5px; }
.demo-d .tag {
display: inline-flex; align-items: center; gap: 6px;
font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
padding: 3px 9px; border-radius: 3px; border: 1px solid var(--line); background: #fff; color: var(--sub);
}
.demo-d .tag i { width: 7px; height: 7px; border-radius: 50%; }
.demo-d .tag-direct { color: var(--direct); }
.demo-d .tag-direct i { background: var(--direct); }
.demo-d .tag-warp { color: var(--warp); }
.demo-d .tag-warp i { background: var(--warp); }
.demo-d .rowact { color: var(--sub); font-size: 12px; cursor: pointer; letter-spacing: 0.06em; }
.demo-d .rowact:hover { color: var(--ink); }
/* ============ 各方向的导航样例(选中态是重点) ============ */
.navdemo { display: flex; align-items: center; gap: 4px; margin-bottom: 28px; }
/* A选中 = 粗下划线(站台线条),不加底不加边 */
.demo-a .navdemo { gap: 22px; border-bottom: 1px solid var(--line); }
.demo-a .navdemo a {
text-decoration: none; color: var(--sub); font-size: 14px; font-weight: 600;
padding: 10px 2px 12px; border-bottom: 3px solid transparent; margin-bottom: -1px;
}
.demo-a .navdemo a:hover { color: var(--ink); }
.demo-a .navdemo a.cur { color: var(--ink); border-bottom-color: var(--ink); }
/* B选中 = 墨蓝衬线加粗 + 传统着重号 */
.demo-b .navdemo { gap: 26px; }
.demo-b .navdemo a {
text-decoration: none; color: var(--sub); font-size: 15px;
font-family: 'Songti SC', 'STSong', Georgia, serif; position: relative; padding: 4px 0;
}
.demo-b .navdemo a:hover { color: var(--ink); }
.demo-b .navdemo a.cur { color: var(--navy); font-weight: 700; }
.demo-b .navdemo a.cur::before {
content: '・'; position: absolute; left: 50%; transform: translateX(-50%);
top: -14px; color: var(--seal-red); font-size: 12px;
}
/* C选中 = 纯字色与字重变化,零装饰 */
.demo-c .navdemo { gap: 6px; }
.demo-c .navdemo a {
text-decoration: none; color: var(--sub); font-size: 13.5px;
padding: 7px 12px; border-radius: 6px;
}
.demo-c .navdemo a:hover { color: var(--text); }
.demo-c .navdemo a.cur { color: #fff; font-weight: 600; }
/* D选中 = 按下去的实体键(内凹) */
.demo-d .navdemo { gap: 6px; }
.demo-d .navdemo a {
text-decoration: none; color: var(--sub); font-size: 13px; font-weight: 600;
padding: 8px 14px; border-radius: 4px; border: 1px solid transparent;
}
.demo-d .navdemo a:hover { color: var(--ink); }
.demo-d .navdemo a.cur {
color: var(--ink); background: #dfe1e4; border-color: var(--line);
box-shadow: 0 1.5px 2px rgba(27,29,31,0.18) inset;
}
.section-label { margin: 0 0 12px; }
.demo-a .section-label { font-size: 13px; font-weight: 700; letter-spacing: 0.08em; color: var(--sub); }
.demo-b .section-label { font-family: 'Songti SC', 'STSong', Georgia, serif; font-size: 15px; font-weight: 700; color: var(--navy); }
.demo-c .section-label { font-size: 11.5px; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; color: var(--sub); }
.demo-d .section-label { font-size: 10.5px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; color: var(--sub); }
</style>
</head>
<body>
<nav class="switcher" role="tablist">
<button class="on" data-t="a" role="tab">A 站牌导视</button>
<button data-t="b" role="tab">B 纸上台账</button>
<button data-t="c" role="tab">C 克制暗色</button>
<button data-t="d" role="tab">D 仪表工业</button>
</nav>
<!-- ================= A 站牌导视 ================= -->
<section class="demo demo-a on" id="demo-a">
<div class="frame">
<header class="demo-intro">
<div class="no">方向 A</div>
<h1>站牌导视</h1>
<p>轨道交通导视系统的语言:白底粗黑、线路色圆标、站名牌。系统本来就叫 Station——站点是车站协议是线路直连和 WARP 是两个站台。信息密度靠导视系统级的层级控制,而不是靠深色背景。</p>
</header>
<nav class="navdemo">
<a href="#" onclick="return false">节点</a>
<a href="#" onclick="return false" class="cur">策略组</a>
<a href="#" onclick="return false">规则</a>
<a href="#" onclick="return false">订阅</a>
<a href="#" onclick="return false">设置</a>
</nav>
<div class="toolbar">
<button class="btn btn-primary">添加节点</button>
<button class="btn btn-secondary">批量导入</button>
<button class="btn btn-plain">复制链接</button>
</div>
<p class="section-label">站点卡</p>
<div class="station-sign">
<span class="code">SG</span>
<span class="name">sg.example.com</span>
<span class="count">7 条线路 · AWS 新加坡</span>
</div>
<div class="sign-strip"><i class="s-direct"></i><i class="s-warp"></i></div>
<div class="card">
<div class="platforms">
<div class="platform platform-direct">
<div class="platform-head">直连出口 DIRECT</div>
<ul>
<li><span class="proto-dot p-v">V</span>VMess<span class="path">/9d3f01</span></li>
<li><span class="proto-dot p-t">T</span>Trojan<span class="path">/8a20c4</span></li>
<li><span class="proto-dot p-s">S</span>Shadowsocks<span class="path">/3f81b2</span></li>
<li><span class="proto-dot p-h">H</span>Hysteria2<span class="path">:443</span></li>
</ul>
</div>
<div class="platform platform-warp">
<div class="platform-head">WARP 净出口</div>
<ul>
<li><span class="proto-dot p-v">V</span>VMess<span class="path">/72e5a9</span></li>
<li><span class="proto-dot p-t">T</span>Trojan<span class="path">/93bf25</span></li>
<li><span class="proto-dot p-s">S</span>Shadowsocks<span class="path">/29c1b3</span></li>
</ul>
</div>
</div>
</div>
<p class="section-label">节点表</p>
<table>
<thead><tr><th>名称</th><th>协议</th><th>出口</th><th>服务器</th><th>路径</th><th></th></tr></thead>
<tbody>
<tr>
<td>SG-vmess-direct</td>
<td><span class="proto-dot p-v">V</span></td>
<td><span class="tag tag-direct"><i></i>直连</span></td>
<td class="mono">cc.sg.example.com:443</td>
<td class="mono">/9d3f01</td>
<td><span class="rowact">编辑</span></td>
</tr>
<tr>
<td>SG-trojan-warp</td>
<td><span class="proto-dot p-t">T</span></td>
<td><span class="tag tag-warp"><i></i>WARP</span></td>
<td class="mono">cc.sg.example.com:443</td>
<td class="mono">/93bf25</td>
<td><span class="rowact">编辑</span></td>
</tr>
<tr>
<td>LA-ss-direct已停用</td>
<td><span class="proto-dot p-s">S</span></td>
<td><span class="tag tag-off">已停用</span></td>
<td class="mono">cc.la.example.com:443</td>
<td class="mono">/3f81b2</td>
<td><span class="rowact">编辑</span></td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- ================= B 纸上台账 ================= -->
<section class="demo demo-b" id="demo-b">
<div class="frame">
<header class="demo-intro">
<div class="no">No. B 登记簿式</div>
<h1>纸上台账</h1>
<p>档案登记簿的秩序感:纸面底色、衬线标题、墨蓝正文、点线引导。出口语义不用高亮色块,而是盖两枚印章——朱砂是直连,藏青是 WARP。数据库本来就是台账界面照着台账长。</p>
</header>
<nav class="navdemo">
<a href="#" onclick="return false">节点</a>
<a href="#" onclick="return false" class="cur">策略组</a>
<a href="#" onclick="return false">规则</a>
<a href="#" onclick="return false">订阅</a>
<a href="#" onclick="return false">设置</a>
</nav>
<div class="toolbar">
<button class="btn btn-primary">添加节点</button>
<button class="btn">批量导入</button>
<button class="btn btn-plain">复制链接</button>
</div>
<p class="section-label">站点卡</p>
<div class="card">
<div class="card-head">
<span class="code">新加坡 SG</span>
<span class="domain">sg.example.com</span>
<span class="no">REG-2026-001</span>
</div>
<ul class="ledger">
<li><span class="idx">01</span>VMess<span class="leader"></span><span class="path">/9d3f01</span><span class="seal seal-direct"></span></li>
<li><span class="idx">02</span>VMess<span class="leader"></span><span class="path">/72e5a9</span><span class="seal seal-warp">W</span></li>
<li><span class="idx">03</span>Trojan<span class="leader"></span><span class="path">/8a20c4</span><span class="seal seal-direct"></span></li>
<li><span class="idx">04</span>Shadowsocks<span class="leader"></span><span class="path">/29c1b3</span><span class="seal seal-warp">W</span></li>
<li><span class="idx">05</span>Hysteria2<span class="leader"></span><span class="path">:443</span><span class="seal seal-direct"></span></li>
</ul>
</div>
<p class="section-label">节点表</p>
<table>
<thead><tr><th>名称</th><th>协议</th><th>出口</th><th>服务器</th><th>路径</th><th></th></tr></thead>
<tbody>
<tr>
<td>SG-vmess-direct</td>
<td><span class="tag tag-proto">VMess</span></td>
<td><span class="tag tag-direct">直连</span></td>
<td class="mono">cc.sg.example.com:443</td>
<td class="mono">/9d3f01</td>
<td><span class="rowact">编辑</span></td>
</tr>
<tr>
<td>SG-trojan-warp</td>
<td><span class="tag tag-proto">Trojan</span></td>
<td><span class="tag tag-warp">WARP</span></td>
<td class="mono">cc.sg.example.com:443</td>
<td class="mono">/93bf25</td>
<td><span class="rowact">编辑</span></td>
</tr>
<tr>
<td>LA-ss-direct已停用</td>
<td><span class="tag tag-proto">Shadowsocks</span></td>
<td><span class="tag tag-proto">已停用</span></td>
<td class="mono">cc.la.example.com:443</td>
<td class="mono">/3f81b2</td>
<td><span class="rowact">编辑</span></td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- ================= C 克制暗色 ================= -->
<section class="demo demo-c" id="demo-c">
<div class="frame">
<header class="demo-intro">
<div class="no">方向 C</div>
<h1>克制暗色</h1>
<p>保留暗色但把「AI 味」全部拿掉中性炭黑而非深蓝、零发光零亮边条、层级全部交给字重与留白。琥珀与青只出现在数据本身WS 路径)上——颜色只在承载信息时才出现。</p>
</header>
<nav class="navdemo">
<a href="#" onclick="return false">节点</a>
<a href="#" onclick="return false" class="cur">策略组</a>
<a href="#" onclick="return false">规则</a>
<a href="#" onclick="return false">订阅</a>
<a href="#" onclick="return false">设置</a>
</nav>
<div class="toolbar">
<button class="btn btn-primary">添加节点</button>
<button class="btn btn-secondary">批量导入</button>
<button class="btn btn-plain">复制链接</button>
</div>
<p class="section-label">站点卡</p>
<div class="card">
<div class="card-head">
<span class="code">SG · 新加坡</span>
<span class="domain">sg.example.com</span>
<span class="count">7 条线路</span>
</div>
<ul class="lines">
<li><span class="proto">VMess</span><span class="path path-direct">/9d3f01</span><span class="eg">直连</span></li>
<li><span class="proto">VMess</span><span class="path path-warp">/72e5a9</span><span class="eg">WARP</span></li>
<li><span class="proto">Trojan</span><span class="path path-direct">/8a20c4</span><span class="eg">直连</span></li>
<li><span class="proto">Shadowsocks</span><span class="path path-warp">/29c1b3</span><span class="eg">WARP</span></li>
<li><span class="proto">Hysteria2</span><span class="path path-direct">:443</span><span class="eg">直连</span></li>
</ul>
</div>
<p class="section-label">节点表</p>
<table>
<thead><tr><th>名称</th><th>协议</th><th>出口</th><th>服务器</th><th>路径</th><th></th></tr></thead>
<tbody>
<tr>
<td>SG-vmess-direct</td>
<td><span class="tag">vmess</span></td>
<td><span class="tag tag-direct">直连</span></td>
<td class="mono">cc.sg.example.com:443</td>
<td class="mono path-direct">/9d3f01</td>
<td><span class="rowact">编辑</span></td>
</tr>
<tr>
<td>SG-trojan-warp</td>
<td><span class="tag">trojan</span></td>
<td><span class="tag tag-warp">WARP</span></td>
<td class="mono">cc.sg.example.com:443</td>
<td class="mono path-warp">/93bf25</td>
<td><span class="rowact">编辑</span></td>
</tr>
<tr>
<td>LA-ss-direct已停用</td>
<td><span class="tag">ss</span></td>
<td><span class="tag">已停用</span></td>
<td class="mono">cc.la.example.com:443</td>
<td class="mono">/3f81b2</td>
<td><span class="rowact">编辑</span></td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- ================= D 仪表工业 ================= -->
<section class="demo demo-d" id="demo-d">
<div class="frame">
<header class="demo-intro">
<div class="no">MODULE D</div>
<h1>仪表工业</h1>
<p>Dieter Rams 式仪器面板:冷灰机身、白色面板、实体按键的按压手感、大写字母间距的小标签。出口状态是两枚指示灯——橙灯直连、绿灯 WARP像老式功放上的通道指示。</p>
</header>
<nav class="navdemo">
<a href="#" onclick="return false">节点</a>
<a href="#" onclick="return false" class="cur">策略组</a>
<a href="#" onclick="return false">规则</a>
<a href="#" onclick="return false">订阅</a>
<a href="#" onclick="return false">设置</a>
</nav>
<div class="toolbar">
<button class="btn btn-primary">添加节点</button>
<button class="btn">批量导入</button>
<button class="btn btn-plain">复制链接</button>
</div>
<p class="section-label">Station Card 站点卡</p>
<div class="card">
<div class="card-head">
<span class="code">SG</span>
<span class="domain">sg.example.com</span>
<div class="meter">
<span class="label">DIRECT</span><i class="lamp lamp-direct"></i>
<span class="label" style="margin-left:10px">WARP</span><i class="lamp lamp-warp"></i>
</div>
</div>
<div class="grid">
<div class="slot"><i class="lamp lamp-direct"></i>VMess<span class="path">/9d3f01</span></div>
<div class="slot"><i class="lamp lamp-warp"></i>VMess<span class="path">/72e5a9</span></div>
<div class="slot"><i class="lamp lamp-direct"></i>Trojan<span class="path">/8a20c4</span></div>
<div class="slot"><i class="lamp lamp-warp"></i>Shadowsocks<span class="path">/29c1b3</span></div>
</div>
</div>
<p class="section-label">Nodes 节点表</p>
<table>
<thead><tr>
<th><span class="label">名称</span></th><th><span class="label">协议</span></th>
<th><span class="label">出口</span></th><th><span class="label">服务器</span></th>
<th><span class="label">路径</span></th><th></th>
</tr></thead>
<tbody>
<tr>
<td>SG-vmess-direct</td>
<td class="mono">vmess</td>
<td><span class="tag tag-direct"><i></i>Direct</span></td>
<td class="mono">cc.sg.example.com:443</td>
<td class="mono">/9d3f01</td>
<td><span class="rowact">EDIT</span></td>
</tr>
<tr>
<td>SG-trojan-warp</td>
<td class="mono">trojan</td>
<td><span class="tag tag-warp"><i></i>WARP</span></td>
<td class="mono">cc.sg.example.com:443</td>
<td class="mono">/93bf25</td>
<td><span class="rowact">EDIT</span></td>
</tr>
<tr>
<td>LA-ss-direct已停用</td>
<td class="mono">ss</td>
<td><span class="tag"><i style="background:#b7bbc0"></i>OFF</span></td>
<td class="mono">cc.la.example.com:443</td>
<td class="mono">/3f81b2</td>
<td><span class="rowact">EDIT</span></td>
</tr>
</tbody>
</table>
</div>
</section>
<script>
document.querySelectorAll('.switcher button').forEach(function (btn) {
btn.addEventListener('click', function () {
document.querySelectorAll('.switcher button').forEach(function (b) { b.classList.remove('on'); });
document.querySelectorAll('.demo').forEach(function (d) { d.classList.remove('on'); });
btn.classList.add('on');
document.getElementById('demo-' + btn.dataset.t).classList.add('on');
window.scrollTo(0, 0);
});
});
</script>
</body>
</html>