feat: 游民导航 H5、微信小程序与小红书小工具
Co-authored-by: Cursor <cursoragent@cursor.com>
36
.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# Build / pack outputs
|
||||||
|
xhs/release/
|
||||||
|
xhs/dist/
|
||||||
|
*.zip
|
||||||
|
|
||||||
|
# Local secrets / one-off assets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
nomadro.png
|
||||||
|
scripts/_*.py
|
||||||
|
|
||||||
8
.oxlintrc.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"plugins": ["react", "typescript", "oxc"],
|
||||||
|
"rules": {
|
||||||
|
"react/rules-of-hooks": "error",
|
||||||
|
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
||||||
|
}
|
||||||
|
}
|
||||||
58
README.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Navmini · 游民导航
|
||||||
|
|
||||||
|
移动端优先的 **Link 导航** H5,内容对齐 [nav.hackrobot.cn](https://nav.hackrobot.cn/)。效果认可后再做微信 / 抖音 / 小红书小程序。
|
||||||
|
|
||||||
|
## 本地预览
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
手机或浏览器窄屏预览最佳。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## 能力
|
||||||
|
|
||||||
|
- 整页长滚动浏览全部入口(图标 + 标题 + 简介)
|
||||||
|
- 左侧抽屉可快速跳到某一分类
|
||||||
|
- 顶栏随滚动显示当前分类
|
||||||
|
- 浅色 / 深色主题记忆
|
||||||
|
- 回到顶部
|
||||||
|
|
||||||
|
## 图标
|
||||||
|
|
||||||
|
图标已离线压缩到 [`public/icons/`](public/icons/)(96×96 WebP)。`links.json` 引用 `/icons/{id}.webp`。
|
||||||
|
|
||||||
|
重新拉取并压缩:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run icons
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
编辑 [`src/data/links.json`](src/data/links.json):
|
||||||
|
|
||||||
|
```
|
||||||
|
categories[] → groups[] → links[]
|
||||||
|
id / title / subtitle / href / icon / action?
|
||||||
|
```
|
||||||
|
|
||||||
|
`action` 默认 `open`;设为 `copy` 时写入剪贴板。
|
||||||
|
|
||||||
|
## 小程序
|
||||||
|
|
||||||
|
- [`xhs/`](xhs/) 小红书小工具(离线导航,手动选中复制网址)
|
||||||
|
- [`weapp/`](weapp/) 微信小程序(抽屉 + 长列表,一键复制网址)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run weapp:sync
|
||||||
|
```
|
||||||
|
|
||||||
|
微信开发者工具打开 `weapp/` 目录。详见 [`weapp/README.md`](weapp/README.md)。
|
||||||
|
|
||||||
|
- `tt/` 抖音小程序(待做)
|
||||||
21
index.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/webp" href="/icons/_brand.webp" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||||
|
<meta name="theme-color" content="#e9eef4" />
|
||||||
|
<meta name="description" content="游民导航 · 数字游民工具与出海资源合集" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Sora:wght@600;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<title>游民导航</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2188
package-lock.json
generated
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "navmini",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "oxlint",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"icons": "node scripts/fetch-icons.mjs",
|
||||||
|
"xhs:assemble": "python xhs/scripts/assemble.py",
|
||||||
|
"xhs:pack": "python xhs/scripts/pack.py",
|
||||||
|
"xhs:build": "python xhs/scripts/assemble.py && python xhs/scripts/pack.py",
|
||||||
|
"weapp:sync": "python weapp/scripts/sync_data.py"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.2.8",
|
||||||
|
"react-dom": "^19.2.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.13.3",
|
||||||
|
"@types/react": "^19.2.18",
|
||||||
|
"@types/react-dom": "^19.2.7",
|
||||||
|
"@vitejs/plugin-react": "^6.1.1",
|
||||||
|
"icojs": "^1.0.1",
|
||||||
|
"oxlint": "^1.81.0",
|
||||||
|
"sharp": "^0.35.5",
|
||||||
|
"typescript": "~6.0.2",
|
||||||
|
"vite": "^8.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/favicon.svg
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
24
public/icons.svg
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||||
|
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||||
|
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||||
|
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/icons/1psim.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/icons/3x-ui.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/icons/9esim.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/icons/_brand.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/icons/aapanel.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/icons/action-cam.webp
Normal file
|
After Width: | Height: | Size: 794 B |
BIN
public/icons/adsense.webp
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/icons/ai-glasses.webp
Normal file
|
After Width: | Height: | Size: 682 B |
BIN
public/icons/aliyun.webp
Normal file
|
After Width: | Height: | Size: 934 B |
BIN
public/icons/bmc.webp
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/bwh.webp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/icons/chatgpt.webp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/icons/cloud-phone.webp
Normal file
|
After Width: | Height: | Size: 724 B |
BIN
public/icons/cloudflare.webp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/icons/colocrossing.webp
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/icons/creem.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/icons/drone.webp
Normal file
|
After Width: | Height: | Size: 772 B |
BIN
public/icons/dsx.webp
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/icons/ffmpeg.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/icons/frp.webp
Normal file
|
After Width: | Height: | Size: 910 B |
BIN
public/icons/ga.webp
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/icons/gpu.webp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/icons/grok.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/icons/iproyal.webp
Normal file
|
After Width: | Height: | Size: 522 B |
BIN
public/icons/jms.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/icons/lineageos.webp
Normal file
|
After Width: | Height: | Size: 798 B |
BIN
public/icons/lisa.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/icons/mihomo.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/icons/mini-pc.webp
Normal file
|
After Width: | Height: | Size: 370 B |
BIN
public/icons/namesilo.webp
Normal file
|
After Width: | Height: | Size: 942 B |
BIN
public/icons/nomad-book.webp
Normal file
|
After Width: | Height: | Size: 832 B |
BIN
public/icons/nomadcna.webp
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/nomadlist.webp
Normal file
|
After Width: | Height: | Size: 600 B |
BIN
public/icons/nomadro.webp
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
public/icons/nowpayments.webp
Normal file
|
After Width: | Height: | Size: 236 B |
BIN
public/icons/obs.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/icons/okx.webp
Normal file
|
After Width: | Height: | Size: 926 B |
BIN
public/icons/paypal.webp
Normal file
|
After Width: | Height: | Size: 616 B |
BIN
public/icons/proxy-cheap.webp
Normal file
|
After Width: | Height: | Size: 1000 B |
BIN
public/icons/qcloud.webp
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/racknerd.webp
Normal file
|
After Width: | Height: | Size: 776 B |
BIN
public/icons/rpi.webp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/icons/safepal.webp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/icons/smartproxy.webp
Normal file
|
After Width: | Height: | Size: 396 B |
BIN
public/icons/smmfollows.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/icons/srs.webp
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
public/icons/stripe.webp
Normal file
|
After Width: | Height: | Size: 750 B |
BIN
public/icons/tello.webp
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/tiktok.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/icons/vcam.webp
Normal file
|
After Width: | Height: | Size: 928 B |
BIN
public/icons/voyracloud.webp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/icons/vultr.webp
Normal file
|
After Width: | Height: | Size: 944 B |
BIN
public/icons/wireguard.webp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/icons/wise.webp
Normal file
|
After Width: | Height: | Size: 726 B |
BIN
public/icons/xorpay.webp
Normal file
|
After Width: | Height: | Size: 546 B |
BIN
public/icons/yesim.webp
Normal file
|
After Width: | Height: | Size: 968 B |
BIN
public/icons/yidu.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/icons/ykk.webp
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
public/icons/youtube.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/icons/yt-auto-dismiss.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/icons/zpay.webp
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
166
scripts/fetch-icons.mjs
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import sharp from 'sharp'
|
||||||
|
import { decodeIco, isIco } from 'icojs'
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
const root = path.resolve(__dirname, '..')
|
||||||
|
const outDir = path.join(root, 'public', 'icons')
|
||||||
|
const linksPath = path.join(root, 'src', 'data', 'links.json')
|
||||||
|
const SIZE = 96
|
||||||
|
const WEBP_QUALITY = 72
|
||||||
|
|
||||||
|
const SOURCE = {
|
||||||
|
nomadro: 'https://nav.hackrobot.cn/assets/images/nomadro2.png',
|
||||||
|
nomadlist: 'https://nav.hackrobot.cn/assets/images/nomadlist.png',
|
||||||
|
nomadcna: 'https://nav.hackrobot.cn/assets/images/nomadcna.jpg',
|
||||||
|
chatgpt: 'https://nav.hackrobot.cn/assets/images/logos/chatgpt.png',
|
||||||
|
grok: 'https://nav.hackrobot.cn/assets/favicon_1759046289851.png',
|
||||||
|
'1psim': 'https://nav.hackrobot.cn/assets/images/1psim.png',
|
||||||
|
yesim: 'https://nav.hackrobot.cn/assets/images/yesim.png',
|
||||||
|
tello: 'https://nav.hackrobot.cn/assets/images/tello.png',
|
||||||
|
'9esim': 'https://nav.hackrobot.cn/assets/images/9esim.png',
|
||||||
|
wise: 'https://nav.hackrobot.cn/assets/images/logos/wise.png',
|
||||||
|
okx: 'https://nav.hackrobot.cn/assets/images/logos/okx.png',
|
||||||
|
safepal: 'https://nav.hackrobot.cn/assets/images/logos/safepal.png',
|
||||||
|
paypal: 'https://nav.hackrobot.cn/assets/images/paypal.png',
|
||||||
|
bmc: 'https://nav.hackrobot.cn/assets/images/buymecoffee.png',
|
||||||
|
jms: 'https://nav.hackrobot.cn/assets/images/logos/proxy.png',
|
||||||
|
ykk: 'https://nav.hackrobot.cn/assets/images/logos/ykk.jpg',
|
||||||
|
vultr: 'https://nav.hackrobot.cn/assets/images/logos/vultr.png',
|
||||||
|
bwh: 'https://nav.hackrobot.cn/assets/images/logos/bandwagonhost.jpg',
|
||||||
|
racknerd: 'https://nav.hackrobot.cn/assets/images/logos/RackNerd.jpg',
|
||||||
|
qcloud: 'https://nav.hackrobot.cn/assets/images/tengxunyun.png',
|
||||||
|
aliyun: 'https://nav.hackrobot.cn/assets/images/aliyun.jpg',
|
||||||
|
colocrossing: 'https://nav.hackrobot.cn/assets/images/logos/colocrossing.png',
|
||||||
|
iproyal: 'https://nav.hackrobot.cn/assets/images/logos/iproyal.jpg',
|
||||||
|
'proxy-cheap': 'https://nav.hackrobot.cn/assets/images/logos/proxy-cheap.jpg',
|
||||||
|
smartproxy: 'https://nav.hackrobot.cn/assets/images/logos/smartproxy.jpg',
|
||||||
|
lisa: 'https://nav.hackrobot.cn/assets/images/lisa.png',
|
||||||
|
voyracloud: 'https://nav.hackrobot.cn/assets/images/voyracloud.jpg',
|
||||||
|
rpi: 'https://nav.hackrobot.cn/assets/images/logos/raspberrypi.png',
|
||||||
|
'mini-pc': 'https://nav.hackrobot.cn/assets/images/logos/lingke.jpg',
|
||||||
|
gpu: 'https://nav.hackrobot.cn/assets/images/logos/nvidia.png',
|
||||||
|
'3x-ui': 'https://nav.hackrobot.cn/assets/images/logos/3x.jpg',
|
||||||
|
mihomo: 'https://nav.hackrobot.cn/assets/images/mihomo.jpg',
|
||||||
|
frp: 'https://nav.hackrobot.cn/assets/images/logos/frp.png',
|
||||||
|
wireguard: 'https://nav.hackrobot.cn/assets/images/wireguard.png',
|
||||||
|
lineageos: 'https://nav.hackrobot.cn/assets/images/logos/lineageos.webp',
|
||||||
|
'ai-glasses': 'https://nav.hackrobot.cn/assets/images/logos/glass.jpg',
|
||||||
|
'action-cam': 'https://nav.hackrobot.cn/assets/images/logos/action.jpg',
|
||||||
|
drone: 'https://nav.hackrobot.cn/assets/images/logos/neo2.jpg',
|
||||||
|
tiktok: 'https://nav.hackrobot.cn/assets/images/logos/tiktok.png',
|
||||||
|
'cloud-phone': 'https://nav.hackrobot.cn/assets/images/logos/phone.png',
|
||||||
|
vcam: 'https://nav.hackrobot.cn/assets/images/vcam.jpg',
|
||||||
|
youtube: 'https://nav.hackrobot.cn/assets/images/logos/youtube.png',
|
||||||
|
smmfollows: 'https://nav.hackrobot.cn/assets/images/logos/qqtube.png',
|
||||||
|
'yt-auto-dismiss': 'https://nav.hackrobot.cn/assets/images/greasyfork.png',
|
||||||
|
namesilo: 'https://nav.hackrobot.cn/assets/images/logos/namesilo.jpg',
|
||||||
|
aapanel: 'https://nav.hackrobot.cn/assets/images/logos/aapanel.png',
|
||||||
|
cloudflare: 'https://nav.hackrobot.cn/assets/images/logos/cloudflar.ico',
|
||||||
|
ga: 'https://nav.hackrobot.cn/assets/images/logos/analytics.ico',
|
||||||
|
adsense: 'https://nav.hackrobot.cn/assets/images/logos/gads.ico',
|
||||||
|
obs: 'https://nav.hackrobot.cn/assets/images/obs.jpg',
|
||||||
|
ffmpeg: 'https://nav.hackrobot.cn/assets/images/ffmpeg.jpg',
|
||||||
|
srs: 'https://nav.hackrobot.cn/assets/images/srs.png',
|
||||||
|
nowpayments: 'https://nav.hackrobot.cn/assets/images/nowpayments.ico',
|
||||||
|
creem: 'https://nav.hackrobot.cn/assets/images/creem.ico',
|
||||||
|
stripe: 'https://nav.hackrobot.cn/assets/images/stripe.ico',
|
||||||
|
zpay: 'https://nav.hackrobot.cn/assets/images/zpay.ico',
|
||||||
|
xorpay: 'https://nav.hackrobot.cn/assets/images/xorpay.ico',
|
||||||
|
yidu: 'https://nav.hackrobot.cn/assets/images/blog.jpg',
|
||||||
|
dsx: 'https://nav.hackrobot.cn/assets/images/dsx.jpg',
|
||||||
|
_brand: 'https://nav.hackrobot.cn/assets/images/nomadro.png',
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchBuffer(url) {
|
||||||
|
const res = await fetch(url)
|
||||||
|
if (!res.ok) throw new Error(`${res.status} ${url}`)
|
||||||
|
return Buffer.from(await res.arrayBuffer())
|
||||||
|
}
|
||||||
|
|
||||||
|
async function normalizeInput(buf) {
|
||||||
|
if (isIco(buf)) {
|
||||||
|
const images = await decodeIco(buf)
|
||||||
|
let best = images[0]
|
||||||
|
let bestArea = 0
|
||||||
|
for (const img of images) {
|
||||||
|
const meta = await sharp(Buffer.from(img.buffer), { failOn: 'none' }).metadata()
|
||||||
|
const area = (meta.width || 0) * (meta.height || 0)
|
||||||
|
if (area >= bestArea) {
|
||||||
|
bestArea = area
|
||||||
|
best = img
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Buffer.from(best.buffer)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toWebp(buf) {
|
||||||
|
const input = await normalizeInput(buf)
|
||||||
|
return sharp(input, { animated: false, failOn: 'none' })
|
||||||
|
.rotate()
|
||||||
|
.resize(SIZE, SIZE, {
|
||||||
|
fit: 'cover',
|
||||||
|
withoutEnlargement: false,
|
||||||
|
background: { r: 255, g: 255, b: 255, alpha: 0 },
|
||||||
|
})
|
||||||
|
.webp({ quality: WEBP_QUALITY, effort: 6 })
|
||||||
|
.toBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await mkdir(outDir, { recursive: true })
|
||||||
|
const data = JSON.parse(await readFile(linksPath, 'utf8'))
|
||||||
|
|
||||||
|
const ids = new Set(['_brand'])
|
||||||
|
for (const cat of data.categories) {
|
||||||
|
for (const group of cat.groups) {
|
||||||
|
for (const link of group.links) ids.add(link.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ok = 0
|
||||||
|
let fail = 0
|
||||||
|
let bytes = 0
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const url = SOURCE[id]
|
||||||
|
if (!url) {
|
||||||
|
console.error(`✗ ${id}: missing source URL`)
|
||||||
|
fail += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const raw = await fetchBuffer(url)
|
||||||
|
const webp = await toWebp(raw)
|
||||||
|
await writeFile(path.join(outDir, `${id}.webp`), webp)
|
||||||
|
bytes += webp.length
|
||||||
|
ok += 1
|
||||||
|
console.log(`✓ ${id}.webp ${(webp.length / 1024).toFixed(1)}KB`)
|
||||||
|
} catch (err) {
|
||||||
|
fail += 1
|
||||||
|
console.error(`✗ ${id}: ${err.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const cat of data.categories) {
|
||||||
|
for (const group of cat.groups) {
|
||||||
|
for (const link of group.links) {
|
||||||
|
link.icon = `/icons/${link.id}.webp`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await writeFile(linksPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\nDone: ${ok} ok, ${fail} fail, total ${(bytes / 1024).toFixed(1)}KB → public/icons/`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
247
src/App.tsx
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
import { CategoryPanel } from './components/CategoryPanel'
|
||||||
|
import { SideDrawer } from './components/SideDrawer'
|
||||||
|
import config from './data/links.json'
|
||||||
|
import type { NavConfig, NavLink } from './types'
|
||||||
|
|
||||||
|
const nav = config as NavConfig
|
||||||
|
|
||||||
|
type Theme = 'light' | 'dark'
|
||||||
|
|
||||||
|
function readTheme(): Theme {
|
||||||
|
const saved = localStorage.getItem('navmini-theme')
|
||||||
|
if (saved === 'light' || saved === 'dark') return saved
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
? 'dark'
|
||||||
|
: 'light'
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconMenu({ open }: { open: boolean }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={`menu-icon${open ? ' open' : ''}`}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
aria-hidden
|
||||||
|
>
|
||||||
|
<path className="menu-line top" d="M4 7h16" />
|
||||||
|
<path className="menu-line mid" d="M4 12h16" />
|
||||||
|
<path className="menu-line bot" d="M4 17h12" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconSun() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden>
|
||||||
|
<circle cx="12" cy="12" r="4" stroke="currentColor" strokeWidth="1.8" />
|
||||||
|
<path
|
||||||
|
d="M12 2.5v2.2M12 19.3v2.2M2.5 12h2.2M19.3 12h2.2M5.2 5.2l1.6 1.6M17.2 17.2l1.6 1.6M18.8 5.2l-1.6 1.6M6.8 17.2l-1.6 1.6"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.8"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconMoon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden>
|
||||||
|
<path
|
||||||
|
d="M18.5 14.2A7.2 7.2 0 0 1 9.8 5.5 7.4 7.4 0 1 0 18.5 14.2Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.8"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconUp() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden>
|
||||||
|
<path
|
||||||
|
d="M12 19V5M6.5 10.5 12 5l5.5 5.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.8"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const [theme, setTheme] = useState<Theme>('light')
|
||||||
|
const [activeId, setActiveId] = useState(nav.categories[0]?.id ?? '')
|
||||||
|
const [drawerOpen, setDrawerOpen] = useState(false)
|
||||||
|
const [toast, setToast] = useState('')
|
||||||
|
const [showTop, setShowTop] = useState(false)
|
||||||
|
const [pillKey, setPillKey] = useState(0)
|
||||||
|
const jumpingRef = useRef(false)
|
||||||
|
const themeBtnRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTheme(readTheme())
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.dataset.theme = theme
|
||||||
|
localStorage.setItem('navmini-theme', theme)
|
||||||
|
const meta = document.querySelector('meta[name="theme-color"]')
|
||||||
|
if (meta) meta.setAttribute('content', theme === 'dark' ? '#0a0f14' : '#e9eef4')
|
||||||
|
}, [theme])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!toast) return
|
||||||
|
const id = window.setTimeout(() => setToast(''), 1600)
|
||||||
|
return () => window.clearTimeout(id)
|
||||||
|
}, [toast])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const sections = nav.categories
|
||||||
|
.map((c) => document.getElementById(`cat-${c.id}`))
|
||||||
|
.filter((el): el is HTMLElement => !!el)
|
||||||
|
|
||||||
|
const onScroll = () => {
|
||||||
|
setShowTop(window.scrollY > 480)
|
||||||
|
if (jumpingRef.current || sections.length === 0) return
|
||||||
|
|
||||||
|
const marker = 88
|
||||||
|
let current = sections[0].id.replace(/^cat-/, '')
|
||||||
|
for (const el of sections) {
|
||||||
|
const top = el.getBoundingClientRect().top
|
||||||
|
if (top <= marker) current = el.id.replace(/^cat-/, '')
|
||||||
|
else break
|
||||||
|
}
|
||||||
|
setActiveId((prev) => {
|
||||||
|
if (prev === current) return prev
|
||||||
|
setPillKey((k) => k + 1)
|
||||||
|
return current
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onScroll()
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true })
|
||||||
|
return () => window.removeEventListener('scroll', onScroll)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const activeCategory = useMemo(
|
||||||
|
() => nav.categories.find((c) => c.id === activeId) ?? nav.categories[0],
|
||||||
|
[activeId],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onOpen = useCallback((link: NavLink) => {
|
||||||
|
const action = link.action ?? 'open'
|
||||||
|
if (action === 'copy') {
|
||||||
|
navigator.clipboard
|
||||||
|
?.writeText(link.href)
|
||||||
|
.then(() => setToast('已复制'))
|
||||||
|
.catch(() => setToast('复制失败'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.open(link.href, '_blank', 'noopener,noreferrer')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const onSelectCategory = useCallback((id: string) => {
|
||||||
|
setActiveId(id)
|
||||||
|
setPillKey((k) => k + 1)
|
||||||
|
setDrawerOpen(false)
|
||||||
|
jumpingRef.current = true
|
||||||
|
const el = document.getElementById(`cat-${id}`)
|
||||||
|
if (el) {
|
||||||
|
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||||
|
}
|
||||||
|
window.setTimeout(() => {
|
||||||
|
jumpingRef.current = false
|
||||||
|
}, 700)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
themeBtnRef.current?.classList.remove('spin')
|
||||||
|
void themeBtnRef.current?.offsetWidth
|
||||||
|
themeBtnRef.current?.classList.add('spin')
|
||||||
|
setTheme((t) => (t === 'dark' ? 'light' : 'dark'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`app-shell${drawerOpen ? ' drawer-open' : ''}`}>
|
||||||
|
<div className="bg-glow" aria-hidden>
|
||||||
|
<span className="orb orb-a" />
|
||||||
|
<span className="orb orb-b" />
|
||||||
|
<span className="orb orb-c" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header className="topbar sticky-stack">
|
||||||
|
<div className="brand-row">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`icon-btn menu-trigger${drawerOpen ? ' open' : ''}`}
|
||||||
|
aria-label="打开分类"
|
||||||
|
aria-expanded={drawerOpen}
|
||||||
|
onClick={() => setDrawerOpen(true)}
|
||||||
|
>
|
||||||
|
<IconMenu open={drawerOpen} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="brand-mark">
|
||||||
|
<img
|
||||||
|
className="brand-logo"
|
||||||
|
src="/icons/_brand.webp"
|
||||||
|
alt=""
|
||||||
|
width={28}
|
||||||
|
height={28}
|
||||||
|
/>
|
||||||
|
<div className="brand-text">
|
||||||
|
<h1 className="brand-name">{nav.brand}</h1>
|
||||||
|
<p key={pillKey} className="brand-current">
|
||||||
|
{activeCategory.title}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
ref={themeBtnRef}
|
||||||
|
type="button"
|
||||||
|
className="icon-btn theme-trigger"
|
||||||
|
aria-label={theme === 'dark' ? '切换浅色' : '切换深色'}
|
||||||
|
onClick={toggleTheme}
|
||||||
|
>
|
||||||
|
{theme === 'dark' ? <IconSun /> : <IconMoon />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<SideDrawer
|
||||||
|
open={drawerOpen}
|
||||||
|
brand={nav.brand}
|
||||||
|
tagline={nav.tagline}
|
||||||
|
categories={nav.categories}
|
||||||
|
activeId={activeCategory.id}
|
||||||
|
onClose={() => setDrawerOpen(false)}
|
||||||
|
onSelect={onSelectCategory}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<main className="page">
|
||||||
|
{nav.categories.map((cat) => (
|
||||||
|
<CategoryPanel key={cat.id} category={cat} onOpen={onOpen} />
|
||||||
|
))}
|
||||||
|
<footer className="footer">{nav.footer}</footer>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`back-top${showTop ? ' show' : ''}`}
|
||||||
|
aria-label="回到顶部"
|
||||||
|
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
||||||
|
>
|
||||||
|
<IconUp />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className={`toast${toast ? ' show' : ''}`} role="status">
|
||||||
|
{toast}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
BIN
src/assets/hero.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
src/assets/react.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
1
src/assets/vite.svg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
48
src/components/CategoryPanel.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { useReveal } from '../hooks/useReveal'
|
||||||
|
import { LinkCard } from './LinkCard'
|
||||||
|
import type { NavCategory, NavLink } from '../types'
|
||||||
|
|
||||||
|
interface CategoryPanelProps {
|
||||||
|
category: NavCategory
|
||||||
|
onOpen: (link: NavLink) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CategoryPanel({ category, onOpen }: CategoryPanelProps) {
|
||||||
|
const ref = useReveal<HTMLElement>()
|
||||||
|
let flatIndex = 0
|
||||||
|
const count = category.groups.reduce((s, g) => s + g.links.length, 0)
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
return (
|
||||||
|
<section className="cat-panel" id={`cat-${category.id}`} ref={ref}>
|
||||||
|
<header className="cat-heading reveal">
|
||||||
|
<h2>{category.title}</h2>
|
||||||
|
</header>
|
||||||
|
<p className="empty-hint">暂无入口</p>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="cat-panel" id={`cat-${category.id}`} ref={ref}>
|
||||||
|
<header className="cat-heading reveal">
|
||||||
|
<h2>{category.title}</h2>
|
||||||
|
<span className="cat-count">{count}</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{category.groups.map((group) => (
|
||||||
|
<div key={group.id} className="group-block">
|
||||||
|
<h3 className="group-title reveal">{group.title}</h3>
|
||||||
|
<div className="link-grid">
|
||||||
|
{group.links.map((link) => {
|
||||||
|
const i = flatIndex++
|
||||||
|
return (
|
||||||
|
<LinkCard key={link.id} link={link} index={i} onOpen={onOpen} />
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
57
src/components/LinkCard.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { useRef, type MouseEvent } from 'react'
|
||||||
|
import type { NavLink } from '../types'
|
||||||
|
|
||||||
|
interface LinkCardProps {
|
||||||
|
link: NavLink
|
||||||
|
index: number
|
||||||
|
onOpen: (link: NavLink) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LinkCard({ link, index, onOpen }: LinkCardProps) {
|
||||||
|
const btnRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
function handleClick(e: MouseEvent<HTMLButtonElement>) {
|
||||||
|
const btn = btnRef.current
|
||||||
|
if (btn && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||||
|
const rect = btn.getBoundingClientRect()
|
||||||
|
const x = e.clientX - rect.left
|
||||||
|
const y = e.clientY - rect.top
|
||||||
|
const ripple = document.createElement('span')
|
||||||
|
ripple.className = 'ripple'
|
||||||
|
ripple.style.left = `${x}px`
|
||||||
|
ripple.style.top = `${y}px`
|
||||||
|
btn.appendChild(ripple)
|
||||||
|
ripple.addEventListener('animationend', () => ripple.remove())
|
||||||
|
}
|
||||||
|
onOpen(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
ref={btnRef}
|
||||||
|
type="button"
|
||||||
|
className="link-card reveal"
|
||||||
|
style={{ ['--reveal-delay' as string]: `${Math.min(index % 6, 5) * 45}ms` }}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<span className="link-icon-wrap">
|
||||||
|
<img
|
||||||
|
className="link-icon"
|
||||||
|
src={link.icon}
|
||||||
|
alt=""
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
onError={(e) => {
|
||||||
|
e.currentTarget.style.visibility = 'hidden'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className="link-meta">
|
||||||
|
<span className="link-title">{link.title}</span>
|
||||||
|
<span className="link-desc">{link.subtitle}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
101
src/components/SideDrawer.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import type { NavCategory } from '../types'
|
||||||
|
|
||||||
|
interface SideDrawerProps {
|
||||||
|
open: boolean
|
||||||
|
brand: string
|
||||||
|
tagline: string
|
||||||
|
categories: NavCategory[]
|
||||||
|
activeId: string
|
||||||
|
onClose: () => void
|
||||||
|
onSelect: (id: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SideDrawer({
|
||||||
|
open,
|
||||||
|
brand,
|
||||||
|
tagline,
|
||||||
|
categories,
|
||||||
|
activeId,
|
||||||
|
onClose,
|
||||||
|
onSelect,
|
||||||
|
}: SideDrawerProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
const prev = document.body.style.overflow
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
const onKey = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') onClose()
|
||||||
|
}
|
||||||
|
window.addEventListener('keydown', onKey)
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = prev
|
||||||
|
window.removeEventListener('keydown', onKey)
|
||||||
|
}
|
||||||
|
}, [open, onClose])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`drawer-root${open ? ' open' : ''}`} aria-hidden={!open}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="drawer-mask"
|
||||||
|
aria-label="关闭菜单"
|
||||||
|
tabIndex={open ? 0 : -1}
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
<aside
|
||||||
|
className="drawer-panel"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="分类导航"
|
||||||
|
>
|
||||||
|
<div className="drawer-head">
|
||||||
|
<div className="drawer-brand-wrap">
|
||||||
|
<img src="/icons/_brand.webp" alt="" width={28} height={28} />
|
||||||
|
<div>
|
||||||
|
<span className="drawer-brand">{brand}</span>
|
||||||
|
<span className="drawer-sub">{tagline}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-btn"
|
||||||
|
aria-label="关闭"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden>
|
||||||
|
<path
|
||||||
|
d="M7 7l10 10M17 7 7 17"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.8"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<nav className="drawer-nav">
|
||||||
|
{categories.map((cat, i) => {
|
||||||
|
const count = cat.groups.reduce((s, g) => s + g.links.length, 0)
|
||||||
|
const active = cat.id === activeId
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={cat.id}
|
||||||
|
type="button"
|
||||||
|
className={`drawer-item${active ? ' active' : ''}`}
|
||||||
|
style={{ ['--i' as string]: i }}
|
||||||
|
aria-current={active ? 'page' : undefined}
|
||||||
|
onClick={() => onSelect(cat.id)}
|
||||||
|
>
|
||||||
|
<span className="drawer-emoji" aria-hidden>
|
||||||
|
{cat.emoji}
|
||||||
|
</span>
|
||||||
|
<span className="drawer-item-title">{cat.title}</span>
|
||||||
|
<span className="drawer-item-count">{count}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
585
src/data/links.json
Normal file
@ -0,0 +1,585 @@
|
|||||||
|
{
|
||||||
|
"brand": "游民导航",
|
||||||
|
"tagline": "数字游民工具与出海资源合集",
|
||||||
|
"footer": "2026 游民导航",
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"id": "nomad",
|
||||||
|
"title": "数字游民",
|
||||||
|
"emoji": "✦",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "community",
|
||||||
|
"title": "游民社区",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "nomadro",
|
||||||
|
"title": "nomadro",
|
||||||
|
"subtitle": "出海导航",
|
||||||
|
"href": "https://nomadro.com",
|
||||||
|
"icon": "/icons/nomadro.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nomadlist",
|
||||||
|
"title": "nomadlist",
|
||||||
|
"subtitle": "海外最大的数字游民社区",
|
||||||
|
"href": "https://nomads.com?ref=https://nomadro.com",
|
||||||
|
"icon": "/icons/nomadlist.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nomadcna",
|
||||||
|
"title": "nomadcna",
|
||||||
|
"subtitle": "国内数字游民社区",
|
||||||
|
"href": "https://meetup.hackrobot.cn",
|
||||||
|
"icon": "/icons/nomadcna.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "toolbox",
|
||||||
|
"title": "工具箱",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "chatgpt",
|
||||||
|
"title": "ChatGPT",
|
||||||
|
"subtitle": "OpenAI 开发的人工智能聊天机器人",
|
||||||
|
"href": "https://chat.openai.com/",
|
||||||
|
"icon": "/icons/chatgpt.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grok",
|
||||||
|
"title": "Grok",
|
||||||
|
"subtitle": "xAI 免费 AI 助手,追求真实与客观",
|
||||||
|
"href": "https://grok.com/",
|
||||||
|
"icon": "/icons/grok.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "us-phone",
|
||||||
|
"title": "美国手机号",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "1psim",
|
||||||
|
"title": "1psim",
|
||||||
|
"subtitle": "低成本 2.5$/m,支持 eSIM 和实体卡(邮寄中国)",
|
||||||
|
"href": "https://1psim.com/7GARRL",
|
||||||
|
"icon": "/icons/1psim.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "yesim",
|
||||||
|
"title": "yesim",
|
||||||
|
"subtitle": "美国手机号,3$/m",
|
||||||
|
"href": "https://yesim.app/?partner_id=1948",
|
||||||
|
"icon": "/icons/yesim.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tello",
|
||||||
|
"title": "tello",
|
||||||
|
"subtitle": "最低 5$/m,支持 eSIM",
|
||||||
|
"href": "https://tello.com/",
|
||||||
|
"icon": "/icons/tello.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9esim",
|
||||||
|
"title": "9esim",
|
||||||
|
"subtitle": "让任何手机支持 eSIM,可写数据的 SIM 实体卡",
|
||||||
|
"href": "https://www.9esim.com/?coupon=XIAOSHUANGERIC2",
|
||||||
|
"icon": "/icons/9esim.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "finance",
|
||||||
|
"title": "金融收款",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "wise",
|
||||||
|
"title": "wise",
|
||||||
|
"subtitle": "线上开通境外银行账户(身份证即可)",
|
||||||
|
"href": "https://wise.com/invite/amc/xiaoshuangh",
|
||||||
|
"icon": "/icons/wise.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "okx",
|
||||||
|
"title": "OKX 交易所",
|
||||||
|
"subtitle": "加密货币交易所",
|
||||||
|
"href": "https://okx.com/join/84999861",
|
||||||
|
"icon": "/icons/okx.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "safepal",
|
||||||
|
"title": "safepal (U卡)",
|
||||||
|
"subtitle": "邀请码 104809,出海神卡,加密货币与法币转换",
|
||||||
|
"href": "https://www.safepal.com/zh-cn/",
|
||||||
|
"icon": "/icons/safepal.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "paypal",
|
||||||
|
"title": "paypal",
|
||||||
|
"subtitle": "出海收款必备,TikTok 提现、佣金提现",
|
||||||
|
"href": "https://py.pl/1tyAwY",
|
||||||
|
"icon": "/icons/paypal.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bmc",
|
||||||
|
"title": "buy me coffee",
|
||||||
|
"subtitle": "赞助收款平台",
|
||||||
|
"href": "https://www.buymeacoffee.com/invite/xiaoshuangi",
|
||||||
|
"icon": "/icons/bmc.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "studio",
|
||||||
|
"title": "工作室",
|
||||||
|
"emoji": "☕",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "proxy",
|
||||||
|
"title": "科学上网",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "jms",
|
||||||
|
"title": "just my socks",
|
||||||
|
"subtitle": "老牌网络代理服务",
|
||||||
|
"href": "https://justmysocks.net/members/aff.php?aff=12475",
|
||||||
|
"icon": "/icons/jms.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ykk",
|
||||||
|
"title": "Ykk Cloud",
|
||||||
|
"subtitle": "性价比高的网络加速服务",
|
||||||
|
"href": "https://panel.ykkk.best/#/register?code=xYQebIgT",
|
||||||
|
"icon": "/icons/ykk.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vps",
|
||||||
|
"title": "VPS 服务器",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "vultr",
|
||||||
|
"title": "vultr",
|
||||||
|
"subtitle": "可按分钟计费的服务器(可支付宝)",
|
||||||
|
"href": "https://www.vultr.com/?ref=8985003",
|
||||||
|
"icon": "/icons/vultr.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bwh",
|
||||||
|
"title": "bandwagonhost",
|
||||||
|
"subtitle": "针对中国线路优化的高质量 VPS",
|
||||||
|
"href": "https://bandwagonhost.com/aff.php?aff=25463",
|
||||||
|
"icon": "/icons/bwh.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "racknerd",
|
||||||
|
"title": "RackNerd",
|
||||||
|
"subtitle": "最便宜的美国 VPS 服务器",
|
||||||
|
"href": "https://my.racknerd.com/aff.php?aff=15630",
|
||||||
|
"icon": "/icons/racknerd.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "qcloud",
|
||||||
|
"title": "腾讯云",
|
||||||
|
"subtitle": "腾讯服务器,国内速度快",
|
||||||
|
"href": "https://curl.qcloud.com/BZZ2MBTN",
|
||||||
|
"icon": "/icons/qcloud.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aliyun",
|
||||||
|
"title": "阿里云",
|
||||||
|
"subtitle": "阿里巴巴旗下 VPS 服务器",
|
||||||
|
"href": "https://www.aliyun.com/minisite/goods?userCode=dii0ofzg",
|
||||||
|
"icon": "/icons/aliyun.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "colocrossing",
|
||||||
|
"title": "colocrossing",
|
||||||
|
"subtitle": "美国 VPS 服务商",
|
||||||
|
"href": "https://cloud.colocrossing.com/aff.php?aff=1846",
|
||||||
|
"icon": "/icons/colocrossing.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "resi-ip",
|
||||||
|
"title": "静态住宅 IP",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "iproyal",
|
||||||
|
"title": "iproyal",
|
||||||
|
"subtitle": "静态住宅 IP,出海必备",
|
||||||
|
"href": "https://iproyal.cn/?r=nomadro",
|
||||||
|
"icon": "/icons/iproyal.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "proxy-cheap",
|
||||||
|
"title": "proxy-cheap",
|
||||||
|
"subtitle": "便宜的住宅 IP",
|
||||||
|
"href": "https://app.proxy-cheap.com/r/bmcMoY",
|
||||||
|
"icon": "/icons/proxy-cheap.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "smartproxy",
|
||||||
|
"title": "smartproxy",
|
||||||
|
"subtitle": "静态住宅 IP 服务商",
|
||||||
|
"href": "https://www.smartproxy.org/register/?invitation_code=7RXPLM",
|
||||||
|
"icon": "/icons/smartproxy.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "resi-vps",
|
||||||
|
"title": "住宅 VPS 服务器",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "lisa",
|
||||||
|
"title": "lisa",
|
||||||
|
"subtitle": "住宅 IP 服务器,TikTok 必备",
|
||||||
|
"href": "https://lisahost.com/aff.php?aff=7566",
|
||||||
|
"icon": "/icons/lisa.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "voyracloud",
|
||||||
|
"title": "voyracloud",
|
||||||
|
"subtitle": "静态住宅 IP 服务器",
|
||||||
|
"href": "https://www.voyracloud.com/?ref_code=2LJPRTE5",
|
||||||
|
"icon": "/icons/voyracloud.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hardware",
|
||||||
|
"title": "硬件",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "rpi",
|
||||||
|
"title": "树莓派",
|
||||||
|
"subtitle": "无与伦比的 ARM 开发板",
|
||||||
|
"href": "https://uland.taobao.com/coupon/edetail?e=5hkWkiThOmClhHvvyUNXZfh8CuWt5YH5OVuOuRD5gLJMmdsrkidbOWBzzpT26idJPSbzO0ArF68ertbZl9wqRUbeJevxT02sHgNIxyFsmaMOsi2OxhyQb%2BhYUTTNN7GRRSHvQe2jOLZ9pbNCYX0I%2BPP%2BWUTgK%2F%2B0I%2BtaUgbudUxA%2B536asYsLWVfKa%2BhVnND4NyB1fwjO3bJLf667Yy8Q5jB6TX2HR3Qrq8LCw4Dujjqyaqc1rGLUwdQHHM8iY%2BUDMxLRSiIntldeBz90au3REAmOgZ0RtdnfeSBK3KXa0%2FA9snq37kKL6zL4bZ3EAS%2F2oYsLHTsIOxroXBFP6oz%2BA%3D%3D&traceId=213e032e17650447230113327e0c4f&union_lens=lensId%3APUB%401765044691%400b521400_0da4_19af4dca231_da77%400290EZjuZ2xbgOlH3NTYAyI%40eyJmbG9vcklkIjoyMDg3MTcxMDEwLCJzcG1CIjoiiX3BvcnRhbF92Ml90b29sX3NlbGVjdGlvbmxpc3RfZGV0YWlsX2luZGV4X2h0bSIsInNyY0Zsb29ySWQiiOiiI4MDY2NCJ9",
|
||||||
|
"icon": "/icons/rpi.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mini-pc",
|
||||||
|
"title": "迷你主机",
|
||||||
|
"subtitle": "具有性价比的 X86 小主机",
|
||||||
|
"href": "https://s.click.taobao.com/t?e=m%3D2%26s%3DCb1Quo%2F0Wb5w4vFB6t2Z2ueEDrYVVa64YUrQeSeIhnK53hKxp7mNFuhCZFSw9yq2woqx0EHqItb0JlhLk0Jl4eVWmNyHytKdTKJ80yOOA0Mtd0e5Ku7CIDk5ffgV5fRI1qrpxiwMoCNxc1AtbZGVS936JshS%2F%2FIdwgQgG5hIzyLNEPXytV9ALtCLThlbPuuZLb93Df8fOziGg7JVKZos%2BjLJJgRsfWlWtajntMnDLEHhuApO%2F8klJ62RdK%2B6%2FP7Z6k6s3rDgsC6jO9AJYjY8CXJ%2BwEVkOqHFPSJJXArlDuzso5nyPLY4NPiLqets6lIJ",
|
||||||
|
"icon": "/icons/mini-pc.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gpu",
|
||||||
|
"title": "GPU 显卡",
|
||||||
|
"subtitle": "AI 时代的算力基础设施",
|
||||||
|
"href": "https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BATUJK1olXwEDUFZVCkkRC18IGloWWwALV19aAUonRzBQRQQlBENHFRxWFlVPRjtUBABAQlRcCEBdCUoUBWkBGFoSVAcdDRsBVXsQB29pbiZRKGMHKlclCEgVWghcGV91UQoyVW5dCUoWAmYPHl4RbTYCU24fZp-esLulkIK0yd6n5V1eOEonA2kPH1oQWQUFV19VCnsQA2Y4El0dWQ4LU1hBC00SCmcJK2slXjYFVFdJDjlWUXsOaWslXTYBZF5cCEoQAGoLH1kRQQYHV1lbAFcXBWgMGl4RXgUKUllYOEkWAmsBK2vL05J3IB9Uajl8UStqSz4dFQ9jitDJGTlnA2sPH0olImF5ECFaSU0VVAdNQDxuW09KFiw8SyljbW1jcih3JWNxNlwaS0hqWBhoX2sQbQEEVW4",
|
||||||
|
"icon": "/icons/gpu.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "software",
|
||||||
|
"title": "软件",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "3x-ui",
|
||||||
|
"title": "3X-UI",
|
||||||
|
"subtitle": "科学上网仪表盘",
|
||||||
|
"href": "https://github.com/MHSanaei/3x-ui",
|
||||||
|
"icon": "/icons/3x-ui.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mihomo",
|
||||||
|
"title": "虚空终端 mihomo",
|
||||||
|
"subtitle": "代理软件以及配置 wiki",
|
||||||
|
"href": "https://wiki.metacubex.one/config/",
|
||||||
|
"icon": "/icons/mihomo.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "frp",
|
||||||
|
"title": "frp",
|
||||||
|
"subtitle": "高性能内网穿透",
|
||||||
|
"href": "https://gofrp.org/zh-cn/docs/",
|
||||||
|
"icon": "/icons/frp.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wireguard",
|
||||||
|
"title": "wireguard",
|
||||||
|
"subtitle": "P2P 打洞组网",
|
||||||
|
"href": "https://github.com/WireGuard/wireguard-go",
|
||||||
|
"icon": "/icons/wireguard.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lineageos",
|
||||||
|
"title": "lineageos (pi5)",
|
||||||
|
"subtitle": "树莓派的安卓 LineageOS",
|
||||||
|
"href": "https://konstakang.com/devices/rpi5/",
|
||||||
|
"icon": "/icons/lineageos.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gear",
|
||||||
|
"title": "数码装备",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "ai-glasses",
|
||||||
|
"title": "AI 眼镜",
|
||||||
|
"subtitle": "第一视角拍摄",
|
||||||
|
"href": "https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BATkJK1olXw8AXVxfCU8UA18IGloVWwcKXFlZDUgnRzBQRQQlBENHFRxWFlVPRjtUBABAQlRcCEBdCUoXBW4AE1wRWAUdDRsBVXtwXw1pZzNKAmZgFFwlYUh8UzVBZyZlUQoyVW5dCUoWAmYPHl4RbTYCU24fZpO9hbeBtYKjxt-jwInikZ-fjV8JK1sTWgIDUVtVAEkVA2w4HFscbQ8EXFpVAUwRH2wOHlIdXDYyZF1tD0seF2l6WgkBW3QyZF5tC3sXAm8AGFsVXAEBXEJdDUMVB24UG10SWQcHUVZbAE8fCl8KGloRVDYyitPtY0JrQAYIYj0dXGZKMVc5T5Was35jYlMVXQUTZAQOch9SZTMNbC9gIGdQJi5VCzkRSCpfWDUXVAJlDQoCbw1gcG9qRwYdHnYyUW5aDEgn",
|
||||||
|
"icon": "/icons/ai-glasses.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "action-cam",
|
||||||
|
"title": "运动相机",
|
||||||
|
"subtitle": "骑行记录仪",
|
||||||
|
"href": "https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BASgJK1olXDYCVV9eCE0UB28ME1slGVlaCgFtUQ5SQi0DBUVNGFJeSwUIFxlJX3EIGloWXQABUF5ZAEsIWipURmt3CFhXIQkpbChuRy8JGDpRJUJQDwkbBEcnAl8IGloUXA8FUVtZOHsXBF9edVsUXAcDVV1fDEonAl8IHVwRXAMGVVpaDEsUM2gIEmscWw4GXFdaDlcUBWoBE1olbTYBZFldAV8RcS5aD11nbTYCZF1tCEoXAmsNHl8UXQQeVF1ZDE0UH28OHF8UWAIDUVddCkgnAW4JH1IlbdiMwDxcaAJfBTlNTRpCBHpHLhyDht8GcR8BHFIUTDZ6NSMpYRJweWtBfAUXC2JyXBkpfTNRWi1mGV4UAlgENFpeDA13cRRcTAJLbQMyU1peOA",
|
||||||
|
"icon": "/icons/action-cam.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "drone",
|
||||||
|
"title": "无人机",
|
||||||
|
"subtitle": "天空视角",
|
||||||
|
"href": "https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BASgJK1olXDYCVV9eAUIeB2YMH14lGVlaCgFtUQ5SQi0DBUVNGFJeSwUIFxlJX3EIGloWVA8LUFdZDE4IWipURmtCHHVEABsFVChPYRJAbVhlGRhbFRgLBEcnAl8IGloUXA8FUVtZOHsXBF9edVsUXAcDVV1fDEonAl8IHVwRXAMGVVdbC04VM2gIEmscWw4GXFdaDlcUBWoBE1olbTYBZFldAV8RcS5aD11nbTYCZF1tCEoXAmgLHlgRXgIeVFpfDU0UH28OHF8UWAIDUlteAUwnAW4JH1IlbdiMwCQvcipMBxJhfV9HGgF3FVaDht8GfxgBGVkRTDZUCjsVbzBTYRJbeRkWGG9WAx02D0JcUy5mGRNRFEJiUwgldStzfx1wTj1GbQMyU1peOA",
|
||||||
|
"icon": "/icons/drone.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "overseas",
|
||||||
|
"title": "出海掘金",
|
||||||
|
"emoji": "◎",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "tiktok",
|
||||||
|
"title": "TikTok",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "tiktok",
|
||||||
|
"title": "TikTok",
|
||||||
|
"subtitle": "全球直播平台",
|
||||||
|
"href": "https://www.tiktok.com/",
|
||||||
|
"icon": "/icons/tiktok.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cloud-phone",
|
||||||
|
"title": "云手机",
|
||||||
|
"subtitle": "用开源硬件打造云手机",
|
||||||
|
"href": "https://www.hackrobot.cn/",
|
||||||
|
"icon": "/icons/cloud-phone.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vcam",
|
||||||
|
"title": "vcam 虚拟摄像头",
|
||||||
|
"subtitle": "Xposed 模块,替换摄像头视频流",
|
||||||
|
"href": "https://pan.baidu.com/s/1NW4GMGA3cxfUQNWZPD9vOg?pwd=live",
|
||||||
|
"icon": "/icons/vcam.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "youtube",
|
||||||
|
"title": "YouTube",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "youtube",
|
||||||
|
"title": "YouTube",
|
||||||
|
"subtitle": "全球最大的视频网站",
|
||||||
|
"href": "https://www.youtube.com/",
|
||||||
|
"icon": "/icons/youtube.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "smmfollows",
|
||||||
|
"title": "smmfollows",
|
||||||
|
"subtitle": "刷粉刷量平台",
|
||||||
|
"href": "https://smmfollows.com/ref/dfugq",
|
||||||
|
"icon": "/icons/smmfollows.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "yt-auto-dismiss",
|
||||||
|
"title": "YouTube Studio Auto Dismiss",
|
||||||
|
"subtitle": "油猴脚本,无人直播辅助",
|
||||||
|
"href": "https://greasyfork.org/zh-CN/scripts/557378-youtube-studio-auto-dismiss",
|
||||||
|
"icon": "/icons/yt-auto-dismiss.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "web",
|
||||||
|
"title": "Web 网站",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "namesilo",
|
||||||
|
"title": "namesilo",
|
||||||
|
"subtitle": "注册域名",
|
||||||
|
"href": "https://www.namesilo.com/?rid=3bd4047mc",
|
||||||
|
"icon": "/icons/namesilo.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aapanel",
|
||||||
|
"title": "aapanel",
|
||||||
|
"subtitle": "宝塔面板",
|
||||||
|
"href": "https://www.aapanel.com?referral_code=wcn04vez",
|
||||||
|
"icon": "/icons/aapanel.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cloudflare",
|
||||||
|
"title": "Cloudflare",
|
||||||
|
"subtitle": "互联网基础节点",
|
||||||
|
"href": "https://www.cloudflare.com/",
|
||||||
|
"icon": "/icons/cloudflare.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ga",
|
||||||
|
"title": "Google Analytics",
|
||||||
|
"subtitle": "网站流量统计服务",
|
||||||
|
"href": "https://analytics.google.com/",
|
||||||
|
"icon": "/icons/ga.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "adsense",
|
||||||
|
"title": "Google AdSense",
|
||||||
|
"subtitle": "Google 广告服务",
|
||||||
|
"href": "https://www.google.com/adsense/",
|
||||||
|
"icon": "/icons/adsense.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "live",
|
||||||
|
"title": "无人直播",
|
||||||
|
"emoji": "▷",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "live-soft",
|
||||||
|
"title": "软件",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "obs",
|
||||||
|
"title": "OBS",
|
||||||
|
"subtitle": "免费开源:录屏 / 推流直播",
|
||||||
|
"href": "https://pan.quark.cn/s/03e89f1bbc9b",
|
||||||
|
"icon": "/icons/obs.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ffmpeg",
|
||||||
|
"title": "ffmpeg",
|
||||||
|
"subtitle": "命令行视频处理 / 推流,Python 自动化",
|
||||||
|
"href": "https://www.ffmpeg.org/download.html",
|
||||||
|
"icon": "/icons/ffmpeg.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "srs",
|
||||||
|
"title": "SRS",
|
||||||
|
"subtitle": "开源直播服务器,拉流推流中转",
|
||||||
|
"href": "https://ossrs.io/",
|
||||||
|
"icon": "/icons/srs.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pay",
|
||||||
|
"title": "支付网关",
|
||||||
|
"emoji": "◇",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "merchant",
|
||||||
|
"title": "小微商户",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "nowpayments",
|
||||||
|
"title": "NOWPayments",
|
||||||
|
"subtitle": "加密货币收款",
|
||||||
|
"href": "https://account.nowpayments.io/create-account?link_id=3680795921",
|
||||||
|
"icon": "/icons/nowpayments.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "creem",
|
||||||
|
"title": "Creem",
|
||||||
|
"subtitle": "软件出海收款,税务合规",
|
||||||
|
"href": "https://www.creem.io/",
|
||||||
|
"icon": "/icons/creem.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stripe",
|
||||||
|
"title": "Stripe",
|
||||||
|
"subtitle": "全球在线支付",
|
||||||
|
"href": "https://stripe.com/",
|
||||||
|
"icon": "/icons/stripe.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "zpay",
|
||||||
|
"title": "z-pay",
|
||||||
|
"subtitle": "支持独角数卡 · 易支付",
|
||||||
|
"href": "https://z-pay.cn/?uid=22119",
|
||||||
|
"icon": "/icons/zpay.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "xorpay",
|
||||||
|
"title": "xorpay",
|
||||||
|
"subtitle": "支持收银台支付",
|
||||||
|
"href": "https://xorpay.com?r=nomadro",
|
||||||
|
"icon": "/icons/xorpay.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "books",
|
||||||
|
"title": "书籍",
|
||||||
|
"emoji": "◈",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "handbook",
|
||||||
|
"title": "手册",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "nomad-book",
|
||||||
|
"title": "数字游民手册",
|
||||||
|
"subtitle": "复制链接后在小红书打开",
|
||||||
|
"href": "https://www.xiaohongshu.com/s/poster?bgimg=https%3A%2F%2Fsns-redskillhub-s1.xhscdn.com%2Fred_app_image%2F1040g4m8325ke1q36l0404a4druqrlvd5valvb80%3Fsign%3Dbc0c41c2e219f6ca904f52176bd17f54%26t%3D6c9a7a85&xhsshare=&appuid=5acc75ba4eacab15c751fda5&apptime=1790527237&deeplink=xhsdiscover%3A%2F%2FminiTool%2F6ab8984da1e3af0015584e92%3Fsource%3Dh5%26page_key%3D28%26xhsMpScreenMode%3Dfull%26share_id%3D5e90e5979b054c8baf8b3db4c8993d79%26share_channel%3Dcopy_link",
|
||||||
|
"icon": "/icons/nomad-book.webp",
|
||||||
|
"action": "copy"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "forum",
|
||||||
|
"title": "论坛社区",
|
||||||
|
"emoji": "◉",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "blog",
|
||||||
|
"title": "博客",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "yidu",
|
||||||
|
"title": "异度世界",
|
||||||
|
"subtitle": "独立博客,Hugo 搭建",
|
||||||
|
"href": "https://blog.hackrobot.cn",
|
||||||
|
"icon": "/icons/yidu.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dsx",
|
||||||
|
"title": "大师兄",
|
||||||
|
"subtitle": "AI 实践",
|
||||||
|
"href": "https://dsx2016.com",
|
||||||
|
"icon": "/icons/dsx.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
src/hooks/usePrefersReducedMotion.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export function usePrefersReducedMotion() {
|
||||||
|
const [reduced, setReduced] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mq = window.matchMedia('(prefers-reduced-motion: reduce)')
|
||||||
|
const update = () => setReduced(mq.matches)
|
||||||
|
update()
|
||||||
|
mq.addEventListener('change', update)
|
||||||
|
return () => mq.removeEventListener('change', update)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return reduced
|
||||||
|
}
|
||||||
37
src/hooks/useReveal.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { usePrefersReducedMotion } from './usePrefersReducedMotion'
|
||||||
|
|
||||||
|
/** Reveal children with `.reveal` when the root enters the viewport. */
|
||||||
|
export function useReveal<T extends HTMLElement>() {
|
||||||
|
const ref = useRef<T | null>(null)
|
||||||
|
const reduced = usePrefersReducedMotion()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = ref.current
|
||||||
|
if (!root) return
|
||||||
|
|
||||||
|
if (reduced) {
|
||||||
|
root.classList.add('is-visible')
|
||||||
|
root.querySelectorAll('.reveal').forEach((el) => el.classList.add('is-visible'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const targets = [root, ...root.querySelectorAll<HTMLElement>('.reveal')]
|
||||||
|
|
||||||
|
const io = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isIntersecting) continue
|
||||||
|
entry.target.classList.add('is-visible')
|
||||||
|
io.unobserve(entry.target)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.12, rootMargin: '0px 0px -6% 0px' },
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const el of targets) io.observe(el)
|
||||||
|
return () => io.disconnect()
|
||||||
|
}, [reduced])
|
||||||
|
|
||||||
|
return ref
|
||||||
|
}
|
||||||
10
src/main.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import App from './App.tsx'
|
||||||
|
import './styles/global.css'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
951
src/styles/global.css
Normal file
@ -0,0 +1,951 @@
|
|||||||
|
:root,
|
||||||
|
[data-theme='light'] {
|
||||||
|
--bg: #e9eef4;
|
||||||
|
--bg-elevated: #f7fafc;
|
||||||
|
--ink: #15202b;
|
||||||
|
--ink-muted: #5b6b7c;
|
||||||
|
--ink-faint: #8a98a8;
|
||||||
|
--line: rgba(21, 32, 43, 0.08);
|
||||||
|
--surface: rgba(255, 255, 255, 0.78);
|
||||||
|
--surface-solid: #ffffff;
|
||||||
|
--surface-hover: #ffffff;
|
||||||
|
--accent: #0d8f7b;
|
||||||
|
--accent-deep: #0a6e5f;
|
||||||
|
--accent-soft: rgba(13, 143, 123, 0.12);
|
||||||
|
--glow-a: rgba(13, 143, 123, 0.2);
|
||||||
|
--glow-b: rgba(56, 120, 180, 0.14);
|
||||||
|
--shadow: 0 8px 24px rgba(21, 32, 43, 0.06);
|
||||||
|
--shadow-card: 0 1px 2px rgba(21, 32, 43, 0.04), 0 6px 16px rgba(21, 32, 43, 0.04);
|
||||||
|
--toast-bg: #15202b;
|
||||||
|
--toast-ink: #f4f7fa;
|
||||||
|
--chip: rgba(21, 32, 43, 0.045);
|
||||||
|
--chip-active: #15202b;
|
||||||
|
--chip-active-ink: #f7fafc;
|
||||||
|
--topbar-bg: rgba(233, 238, 244, 0.78);
|
||||||
|
--rail: #0d8f7b;
|
||||||
|
--font-display: 'Sora', system-ui, sans-serif;
|
||||||
|
--font-body: 'Manrope', 'PingFang SC', 'Noto Sans SC', system-ui, sans-serif;
|
||||||
|
--radius: 14px;
|
||||||
|
--max-w: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] {
|
||||||
|
--bg: #0a0f14;
|
||||||
|
--bg-elevated: #121820;
|
||||||
|
--ink: #e8eef4;
|
||||||
|
--ink-muted: #8b99a8;
|
||||||
|
--ink-faint: #5d6c7c;
|
||||||
|
--line: rgba(232, 238, 244, 0.1);
|
||||||
|
--surface: rgba(18, 24, 32, 0.88);
|
||||||
|
--surface-solid: #121820;
|
||||||
|
--surface-hover: #18212c;
|
||||||
|
--accent: #3dceb8;
|
||||||
|
--accent-deep: #2aa892;
|
||||||
|
--accent-soft: rgba(61, 206, 184, 0.14);
|
||||||
|
--glow-a: rgba(61, 206, 184, 0.14);
|
||||||
|
--glow-b: rgba(70, 130, 200, 0.12);
|
||||||
|
--shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
|
||||||
|
--shadow-card: 0 1px 0 rgba(255, 255, 255, 0.03), 0 8px 20px rgba(0, 0, 0, 0.28);
|
||||||
|
--toast-bg: #e8eef4;
|
||||||
|
--toast-ink: #0a0f14;
|
||||||
|
--chip: rgba(232, 238, 244, 0.06);
|
||||||
|
--chip-active: #e8eef4;
|
||||||
|
--chip-active-ink: #0a0f14;
|
||||||
|
--topbar-bg: rgba(10, 15, 20, 0.82);
|
||||||
|
--rail: #3dceb8;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#root {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
color: var(--ink);
|
||||||
|
background: var(--bg);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-shell {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100dvh;
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-glow {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background:
|
||||||
|
radial-gradient(680px 380px at 8% -12%, var(--glow-a), transparent 58%),
|
||||||
|
radial-gradient(560px 320px at 100% 4%, var(--glow-b), transparent 55%),
|
||||||
|
radial-gradient(420px 280px at 50% 100%, rgba(13, 143, 123, 0.06), transparent 60%),
|
||||||
|
var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-glow::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0.35;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
|
||||||
|
background-size: 180px 180px;
|
||||||
|
mix-blend-mode: soft-light;
|
||||||
|
animation: grain 8s steps(6) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(2px);
|
||||||
|
opacity: 0.55;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb-a {
|
||||||
|
width: 220px;
|
||||||
|
height: 220px;
|
||||||
|
left: -70px;
|
||||||
|
top: 12%;
|
||||||
|
background: radial-gradient(circle, var(--glow-a), transparent 70%);
|
||||||
|
animation: drift-a 16s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb-b {
|
||||||
|
width: 260px;
|
||||||
|
height: 260px;
|
||||||
|
right: -90px;
|
||||||
|
top: 38%;
|
||||||
|
background: radial-gradient(circle, var(--glow-b), transparent 70%);
|
||||||
|
animation: drift-b 19s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb-c {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
left: 35%;
|
||||||
|
bottom: 8%;
|
||||||
|
background: radial-gradient(circle, rgba(13, 143, 123, 0.18), transparent 70%);
|
||||||
|
animation: drift-c 14s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
padding: 10px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 11px;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--ink);
|
||||||
|
transition:
|
||||||
|
transform 0.15s ease,
|
||||||
|
background 0.2s ease,
|
||||||
|
border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn:hover {
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 35%, var(--line));
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn:active {
|
||||||
|
transform: scale(0.94);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark img {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 9px;
|
||||||
|
object-fit: cover;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-text {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-name {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 1.02rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-current {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
line-height: 1.25;
|
||||||
|
color: var(--accent-deep);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
animation: pill-in 0.35s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-logo {
|
||||||
|
animation: logo-float 5.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon .menu-line {
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-width: 1.8;
|
||||||
|
stroke-linecap: round;
|
||||||
|
transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.2s ease;
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-trigger.open .menu-line.top {
|
||||||
|
transform: translateY(5px) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-trigger.open .menu-line.mid {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scaleX(0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-trigger.open .menu-line.bot {
|
||||||
|
transform: translateY(-5px) translateX(2px) rotate(-45deg);
|
||||||
|
/* bot path is shorter; stretch feel via rotate only */
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-trigger.spin svg {
|
||||||
|
animation: theme-spin 0.45s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
transition: background-color 0.35s ease, color 0.35s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .brand-current {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-stack {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 30;
|
||||||
|
width: min(100%, var(--max-w));
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--topbar-bg);
|
||||||
|
backdrop-filter: blur(18px) saturate(1.2);
|
||||||
|
-webkit-backdrop-filter: blur(18px) saturate(1.2);
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-root {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 60;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-root.open {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-mask {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: rgba(8, 12, 18, 0.46);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.24s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-root.open .drawer-mask {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: min(80vw, 310px);
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, color-mix(in srgb, var(--accent) 8%, var(--bg-elevated)), var(--bg-elevated) 28%),
|
||||||
|
var(--bg-elevated);
|
||||||
|
border-right: 1px solid var(--line);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
transform: translateX(-104%);
|
||||||
|
transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: calc(12px + env(safe-area-inset-top, 0px)) 0
|
||||||
|
calc(18px + env(safe-area-inset-bottom, 0px));
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-root.open .drawer-panel {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 6px 16px 14px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-brand-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-brand-wrap img {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 8px;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-brand {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-sub {
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
color: var(--ink-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-nav {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 14px 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 12px 12px 12px 14px;
|
||||||
|
border-radius: 14px;
|
||||||
|
color: var(--ink-muted);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-12px);
|
||||||
|
transition:
|
||||||
|
background 0.18s ease,
|
||||||
|
color 0.18s ease,
|
||||||
|
transform 0.12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-root.open .drawer-item {
|
||||||
|
animation: drawer-item-in 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||||
|
animation-delay: calc(0.05s + var(--i, 0) * 0.045s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-item:active {
|
||||||
|
transform: scale(0.985);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-item.active {
|
||||||
|
background: var(--accent-soft);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-item.active::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 4px;
|
||||||
|
top: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
width: 3px;
|
||||||
|
border-radius: 99px;
|
||||||
|
background: var(--rail);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-emoji {
|
||||||
|
width: 1.25rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-item-title {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 0.94rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-item.active .drawer-item-title {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-item-count {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 650;
|
||||||
|
color: var(--ink-faint);
|
||||||
|
background: var(--chip);
|
||||||
|
padding: 2px 7px;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-item.active .drawer-item-count {
|
||||||
|
background: color-mix(in srgb, var(--accent) 18%, transparent);
|
||||||
|
color: var(--accent-deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .drawer-item.active .drawer-item-count {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
width: min(100%, var(--max-w));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 14px 12px 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-panel + .cat-panel {
|
||||||
|
margin-top: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-panel {
|
||||||
|
scroll-margin-top: 62px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-heading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-heading::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0.2em;
|
||||||
|
bottom: 0.2em;
|
||||||
|
width: 3px;
|
||||||
|
border-radius: 99px;
|
||||||
|
background: linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 20%, transparent));
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-heading h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 1.08rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-count {
|
||||||
|
font-size: 0.66rem;
|
||||||
|
background: var(--accent-soft);
|
||||||
|
color: var(--accent-deep);
|
||||||
|
padding: 2px 7px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .cat-count {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-block + .group-block {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
margin: 0 0 7px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--ink-faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 560px) {
|
||||||
|
.link-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-card {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
text-align: left;
|
||||||
|
padding: 9px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
overflow: hidden;
|
||||||
|
transition:
|
||||||
|
transform 0.16s ease,
|
||||||
|
background 0.2s ease,
|
||||||
|
border-color 0.2s ease,
|
||||||
|
box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-card:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 32%, var(--line));
|
||||||
|
box-shadow: 0 8px 22px rgba(13, 143, 123, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-card:hover .link-icon-wrap {
|
||||||
|
transform: scale(1.06) rotate(-2deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-card:active {
|
||||||
|
transform: scale(0.978);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-icon-wrap {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(160deg, var(--chip), transparent);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ripple {
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
margin: -4px 0 0 -4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: color-mix(in srgb, var(--accent) 35%, transparent);
|
||||||
|
pointer-events: none;
|
||||||
|
animation: ripple 0.55s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-meta {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-title {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-desc {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
color: var(--ink-muted);
|
||||||
|
line-height: 1.3;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-hint {
|
||||||
|
margin: 16px 0;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--ink-muted);
|
||||||
|
font-size: 0.84rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 28px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--ink-faint);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-top {
|
||||||
|
position: fixed;
|
||||||
|
right: max(14px, calc((100vw - var(--max-w)) / 2 + 14px));
|
||||||
|
bottom: 20px;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: var(--chip-active);
|
||||||
|
color: var(--chip-active-ink);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateY(10px) scale(0.96);
|
||||||
|
transition:
|
||||||
|
opacity 0.22s ease,
|
||||||
|
transform 0.22s ease;
|
||||||
|
z-index: 40;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-top svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-top.show {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
animation: back-top-in 0.35s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-top:hover svg {
|
||||||
|
animation: nudge-up 0.7s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 24px;
|
||||||
|
transform: translateX(-50%) translateY(10px);
|
||||||
|
padding: 9px 16px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--toast-bg);
|
||||||
|
color: var(--toast-ink);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition:
|
||||||
|
opacity 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
z-index: 50;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
animation: toast-pop 0.35s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scroll reveal */
|
||||||
|
.reveal {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(14px);
|
||||||
|
transition:
|
||||||
|
opacity 0.45s cubic-bezier(0.22, 1, 0.36, 1),
|
||||||
|
transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
transition-delay: var(--reveal-delay, 0ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal.is-visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-heading.reveal.is-visible::before {
|
||||||
|
animation: rail-grow 0.45s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rise {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(12px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes intro-card {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(16px) scale(0.98);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes intro-glow {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.15);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pill-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(4px) scale(0.92);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes logo-float {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes theme-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(-90deg) scale(0.7);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(0) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes drawer-item-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-14px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ripple {
|
||||||
|
to {
|
||||||
|
transform: scale(28);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rail-grow {
|
||||||
|
from {
|
||||||
|
transform: scaleY(0);
|
||||||
|
transform-origin: top;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scaleY(1);
|
||||||
|
transform-origin: top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes drift-a {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translate(0, 0) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(24px, -18px) scale(1.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes drift-b {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translate(0, 0) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(-20px, 22px) scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes drift-c {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(16px, -12px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes back-top-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(12px) scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes nudge-up {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes toast-pop {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(12px) scale(0.94);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes grain {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: translate(-1%, 1%);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: translate(1%, -1%);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translate(-1%, -1%);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: translate(1%, 1%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bg-glow::after,
|
||||||
|
.orb,
|
||||||
|
.brand-logo,
|
||||||
|
.brand-current,
|
||||||
|
.drawer-root.open .drawer-item,
|
||||||
|
.theme-trigger.spin svg,
|
||||||
|
.back-top.show,
|
||||||
|
.toast.show,
|
||||||
|
.cat-heading.reveal.is-visible::before {
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal,
|
||||||
|
.drawer-item {
|
||||||
|
opacity: 1 !important;
|
||||||
|
transform: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon .menu-line {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/types.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export type LinkAction = 'open' | 'copy'
|
||||||
|
|
||||||
|
export interface NavLink {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
href: string
|
||||||
|
icon: string
|
||||||
|
action?: LinkAction
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavGroup {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
links: NavLink[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavCategory {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
emoji: string
|
||||||
|
groups: NavGroup[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavConfig {
|
||||||
|
brand: string
|
||||||
|
tagline: string
|
||||||
|
footer: string
|
||||||
|
categories: NavCategory[]
|
||||||
|
}
|
||||||
27
tsconfig.app.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023", "DOM"],
|
||||||
|
"module": "esnext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"allowArbitraryExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
7
tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
23
tsconfig.node.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"module": "nodenext",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
7
vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||
47
weapp/README.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# 游民导航 · 微信小程序
|
||||||
|
|
||||||
|
原生微信小程序,数据与图标同步自仓库 H5:`src/data/links.json`、`public/icons/`。
|
||||||
|
|
||||||
|
交互对齐 H5:左侧抽屉跳转分类、整页长列表、浅/深色主题、回到顶部。因小程序无法直接打开任意外链,点击入口会弹出底部面板,**一键复制网址**后到浏览器粘贴打开(微信允许 `wx.setClipboardData`)。
|
||||||
|
|
||||||
|
结构参考 [nomadmini/miniapp](../../nomadmini/miniapp) 的 DevTools 接入方式(`project.config.json` + 开发者工具打开目录);实现上用原生小程序而非 Taro,更适合静态导航包。
|
||||||
|
|
||||||
|
## 本地预览
|
||||||
|
|
||||||
|
1. 同步数据与图标:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run weapp:sync
|
||||||
|
```
|
||||||
|
|
||||||
|
或在 `weapp/` 下:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python scripts/sync_data.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 打开 **微信开发者工具** → 导入项目 → 目录选本仓库的 **`weapp/`**(不要选上级)。
|
||||||
|
|
||||||
|
3. AppID:可用测试号,或把 `project.config.json` 里的 `touristappid` 换成你的小程序 AppID。
|
||||||
|
|
||||||
|
4. 详情 → 本地设置 → **不校验合法域名**(开发期)。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
```
|
||||||
|
weapp/
|
||||||
|
├── app.js / app.json / app.wxss
|
||||||
|
├── project.config.json
|
||||||
|
├── data/links.json # sync 生成,勿手改
|
||||||
|
├── icons/ # sync 生成
|
||||||
|
├── pages/index/ # 唯一页面
|
||||||
|
└── scripts/sync_data.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与 H5 / 小红书差异
|
||||||
|
|
||||||
|
| | H5 | 微信 weapp | 小红书 xhs |
|
||||||
|
|--|----|-----------|------------|
|
||||||
|
| 打开链接 | `window.open` | 复制到剪贴板 | 手动长按复制 |
|
||||||
|
| 数据 | `src/data/links.json` | sync → `data/` + `icons/` | sync → zip |
|
||||||
|
| 运行 | Vite | 微信开发者工具 | 创作平台上传 zip |
|
||||||
15
weapp/app.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
App({
|
||||||
|
globalData: {
|
||||||
|
theme: "light",
|
||||||
|
},
|
||||||
|
onLaunch() {
|
||||||
|
try {
|
||||||
|
const saved = wx.getStorageSync("navmini-theme");
|
||||||
|
if (saved === "light" || saved === "dark") {
|
||||||
|
this.globalData.theme = saved;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
13
weapp/app.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"pages": ["pages/index/index"],
|
||||||
|
"window": {
|
||||||
|
"navigationBarTitleText": "游民导航",
|
||||||
|
"navigationBarBackgroundColor": "#e9eef4",
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"backgroundColor": "#e9eef4",
|
||||||
|
"backgroundTextStyle": "dark"
|
||||||
|
},
|
||||||
|
"style": "v2",
|
||||||
|
"sitemapLocation": "sitemap.json",
|
||||||
|
"lazyCodeLoading": "requiredComponents"
|
||||||
|
}
|
||||||
80
weapp/app.wxss
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
page {
|
||||||
|
background: #e9eef4;
|
||||||
|
color: #15202b;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Noto Sans SC",
|
||||||
|
"Helvetica Neue", sans-serif;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.45;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
view,
|
||||||
|
text,
|
||||||
|
button,
|
||||||
|
image,
|
||||||
|
scroll-view {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
line-height: inherit;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-shell {
|
||||||
|
--bg: #e9eef4;
|
||||||
|
--bg-elevated: #f7fafc;
|
||||||
|
--ink: #15202b;
|
||||||
|
--ink-muted: #5b6b7c;
|
||||||
|
--ink-faint: #8a98a8;
|
||||||
|
--line: rgba(21, 32, 43, 0.08);
|
||||||
|
--surface: rgba(255, 255, 255, 0.92);
|
||||||
|
--surface-solid: #ffffff;
|
||||||
|
--accent: #0d8f7b;
|
||||||
|
--accent-deep: #0a6e5f;
|
||||||
|
--accent-soft: rgba(13, 143, 123, 0.12);
|
||||||
|
--shadow: 0 8px 24px rgba(21, 32, 43, 0.06);
|
||||||
|
--shadow-card: 0 1px 2px rgba(21, 32, 43, 0.04), 0 6px 16px rgba(21, 32, 43, 0.04);
|
||||||
|
--toast-bg: #15202b;
|
||||||
|
--toast-ink: #f4f7fa;
|
||||||
|
--chip: rgba(21, 32, 43, 0.045);
|
||||||
|
--chip-active: #15202b;
|
||||||
|
--chip-active-ink: #f7fafc;
|
||||||
|
--topbar-bg: rgba(233, 238, 244, 0.92);
|
||||||
|
--rail: #0d8f7b;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-shell.theme-dark {
|
||||||
|
--bg: #0a0f14;
|
||||||
|
--bg-elevated: #121820;
|
||||||
|
--ink: #e8eef4;
|
||||||
|
--ink-muted: #8b99a8;
|
||||||
|
--ink-faint: #5d6c7c;
|
||||||
|
--line: rgba(232, 238, 244, 0.1);
|
||||||
|
--surface: rgba(18, 24, 32, 0.92);
|
||||||
|
--surface-solid: #121820;
|
||||||
|
--accent: #3dceb8;
|
||||||
|
--accent-deep: #2aa892;
|
||||||
|
--accent-soft: rgba(61, 206, 184, 0.14);
|
||||||
|
--shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
|
||||||
|
--shadow-card: 0 1px 0 rgba(255, 255, 255, 0.03), 0 8px 20px rgba(0, 0, 0, 0.28);
|
||||||
|
--toast-bg: #e8eef4;
|
||||||
|
--toast-ink: #0a0f14;
|
||||||
|
--chip: rgba(232, 238, 244, 0.06);
|
||||||
|
--chip-active: #e8eef4;
|
||||||
|
--chip-active-ink: #0a0f14;
|
||||||
|
--topbar-bg: rgba(10, 15, 20, 0.9);
|
||||||
|
--rail: #3dceb8;
|
||||||
|
}
|
||||||
586
weapp/data/links.json
Normal file
@ -0,0 +1,586 @@
|
|||||||
|
{
|
||||||
|
"brand": "游民导航",
|
||||||
|
"tagline": "数字游民工具与出海资源合集",
|
||||||
|
"footer": "2026 游民导航",
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"id": "nomad",
|
||||||
|
"title": "数字游民",
|
||||||
|
"emoji": "✦",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "community",
|
||||||
|
"title": "游民社区",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "nomadro",
|
||||||
|
"title": "nomadro",
|
||||||
|
"subtitle": "出海导航",
|
||||||
|
"href": "https://nomadro.com",
|
||||||
|
"icon": "/icons/nomadro.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nomadlist",
|
||||||
|
"title": "nomadlist",
|
||||||
|
"subtitle": "海外最大的数字游民社区",
|
||||||
|
"href": "https://nomads.com?ref=https://nomadro.com",
|
||||||
|
"icon": "/icons/nomadlist.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nomadcna",
|
||||||
|
"title": "nomadcna",
|
||||||
|
"subtitle": "国内数字游民社区",
|
||||||
|
"href": "https://meetup.hackrobot.cn",
|
||||||
|
"icon": "/icons/nomadcna.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "toolbox",
|
||||||
|
"title": "工具箱",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "chatgpt",
|
||||||
|
"title": "ChatGPT",
|
||||||
|
"subtitle": "OpenAI 开发的人工智能聊天机器人",
|
||||||
|
"href": "https://chat.openai.com/",
|
||||||
|
"icon": "/icons/chatgpt.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grok",
|
||||||
|
"title": "Grok",
|
||||||
|
"subtitle": "xAI 免费 AI 助手,追求真实与客观",
|
||||||
|
"href": "https://grok.com/",
|
||||||
|
"icon": "/icons/grok.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "us-phone",
|
||||||
|
"title": "美国手机号",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "1psim",
|
||||||
|
"title": "1psim",
|
||||||
|
"subtitle": "低成本 2.5$/m,支持 eSIM 和实体卡(邮寄中国)",
|
||||||
|
"href": "https://1psim.com/7GARRL",
|
||||||
|
"icon": "/icons/1psim.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "yesim",
|
||||||
|
"title": "yesim",
|
||||||
|
"subtitle": "美国手机号,3$/m",
|
||||||
|
"href": "https://yesim.app/?partner_id=1948",
|
||||||
|
"icon": "/icons/yesim.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tello",
|
||||||
|
"title": "tello",
|
||||||
|
"subtitle": "最低 5$/m,支持 eSIM",
|
||||||
|
"href": "https://tello.com/",
|
||||||
|
"icon": "/icons/tello.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9esim",
|
||||||
|
"title": "9esim",
|
||||||
|
"subtitle": "让任何手机支持 eSIM,可写数据的 SIM 实体卡",
|
||||||
|
"href": "https://www.9esim.com/?coupon=XIAOSHUANGERIC2",
|
||||||
|
"icon": "/icons/9esim.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "finance",
|
||||||
|
"title": "金融收款",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "wise",
|
||||||
|
"title": "wise",
|
||||||
|
"subtitle": "线上开通境外银行账户(身份证即可)",
|
||||||
|
"href": "https://wise.com/invite/amc/xiaoshuangh",
|
||||||
|
"icon": "/icons/wise.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "okx",
|
||||||
|
"title": "OKX 交易所",
|
||||||
|
"subtitle": "加密货币交易所",
|
||||||
|
"href": "https://okx.com/join/84999861",
|
||||||
|
"icon": "/icons/okx.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "safepal",
|
||||||
|
"title": "safepal (U卡)",
|
||||||
|
"subtitle": "邀请码 104809,出海神卡,加密货币与法币转换",
|
||||||
|
"href": "https://www.safepal.com/zh-cn/",
|
||||||
|
"icon": "/icons/safepal.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "paypal",
|
||||||
|
"title": "paypal",
|
||||||
|
"subtitle": "出海收款必备,TikTok 提现、佣金提现",
|
||||||
|
"href": "https://py.pl/1tyAwY",
|
||||||
|
"icon": "/icons/paypal.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bmc",
|
||||||
|
"title": "buy me coffee",
|
||||||
|
"subtitle": "赞助收款平台",
|
||||||
|
"href": "https://www.buymeacoffee.com/invite/xiaoshuangi",
|
||||||
|
"icon": "/icons/bmc.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "studio",
|
||||||
|
"title": "工作室",
|
||||||
|
"emoji": "☕",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "proxy",
|
||||||
|
"title": "科学上网",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "jms",
|
||||||
|
"title": "just my socks",
|
||||||
|
"subtitle": "老牌网络代理服务",
|
||||||
|
"href": "https://justmysocks.net/members/aff.php?aff=12475",
|
||||||
|
"icon": "/icons/jms.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ykk",
|
||||||
|
"title": "Ykk Cloud",
|
||||||
|
"subtitle": "性价比高的网络加速服务",
|
||||||
|
"href": "https://panel.ykkk.best/#/register?code=xYQebIgT",
|
||||||
|
"icon": "/icons/ykk.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vps",
|
||||||
|
"title": "VPS 服务器",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "vultr",
|
||||||
|
"title": "vultr",
|
||||||
|
"subtitle": "可按分钟计费的服务器(可支付宝)",
|
||||||
|
"href": "https://www.vultr.com/?ref=8985003",
|
||||||
|
"icon": "/icons/vultr.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bwh",
|
||||||
|
"title": "bandwagonhost",
|
||||||
|
"subtitle": "针对中国线路优化的高质量 VPS",
|
||||||
|
"href": "https://bandwagonhost.com/aff.php?aff=25463",
|
||||||
|
"icon": "/icons/bwh.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "racknerd",
|
||||||
|
"title": "RackNerd",
|
||||||
|
"subtitle": "最便宜的美国 VPS 服务器",
|
||||||
|
"href": "https://my.racknerd.com/aff.php?aff=15630",
|
||||||
|
"icon": "/icons/racknerd.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "qcloud",
|
||||||
|
"title": "腾讯云",
|
||||||
|
"subtitle": "腾讯服务器,国内速度快",
|
||||||
|
"href": "https://curl.qcloud.com/BZZ2MBTN",
|
||||||
|
"icon": "/icons/qcloud.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aliyun",
|
||||||
|
"title": "阿里云",
|
||||||
|
"subtitle": "阿里巴巴旗下 VPS 服务器",
|
||||||
|
"href": "https://www.aliyun.com/minisite/goods?userCode=dii0ofzg",
|
||||||
|
"icon": "/icons/aliyun.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "colocrossing",
|
||||||
|
"title": "colocrossing",
|
||||||
|
"subtitle": "美国 VPS 服务商",
|
||||||
|
"href": "https://cloud.colocrossing.com/aff.php?aff=1846",
|
||||||
|
"icon": "/icons/colocrossing.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "resi-ip",
|
||||||
|
"title": "静态住宅 IP",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "iproyal",
|
||||||
|
"title": "iproyal",
|
||||||
|
"subtitle": "静态住宅 IP,出海必备",
|
||||||
|
"href": "https://iproyal.cn/?r=nomadro",
|
||||||
|
"icon": "/icons/iproyal.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "proxy-cheap",
|
||||||
|
"title": "proxy-cheap",
|
||||||
|
"subtitle": "便宜的住宅 IP",
|
||||||
|
"href": "https://app.proxy-cheap.com/r/bmcMoY",
|
||||||
|
"icon": "/icons/proxy-cheap.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "smartproxy",
|
||||||
|
"title": "smartproxy",
|
||||||
|
"subtitle": "静态住宅 IP 服务商",
|
||||||
|
"href": "https://www.smartproxy.org/register/?invitation_code=7RXPLM",
|
||||||
|
"icon": "/icons/smartproxy.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "resi-vps",
|
||||||
|
"title": "住宅 VPS 服务器",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "lisa",
|
||||||
|
"title": "lisa",
|
||||||
|
"subtitle": "住宅 IP 服务器,TikTok 必备",
|
||||||
|
"href": "https://lisahost.com/aff.php?aff=7566",
|
||||||
|
"icon": "/icons/lisa.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "voyracloud",
|
||||||
|
"title": "voyracloud",
|
||||||
|
"subtitle": "静态住宅 IP 服务器",
|
||||||
|
"href": "https://www.voyracloud.com/?ref_code=2LJPRTE5",
|
||||||
|
"icon": "/icons/voyracloud.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hardware",
|
||||||
|
"title": "硬件",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "rpi",
|
||||||
|
"title": "树莓派",
|
||||||
|
"subtitle": "无与伦比的 ARM 开发板",
|
||||||
|
"href": "https://uland.taobao.com/coupon/edetail?e=5hkWkiThOmClhHvvyUNXZfh8CuWt5YH5OVuOuRD5gLJMmdsrkidbOWBzzpT26idJPSbzO0ArF68ertbZl9wqRUbeJevxT02sHgNIxyFsmaMOsi2OxhyQb%2BhYUTTNN7GRRSHvQe2jOLZ9pbNCYX0I%2BPP%2BWUTgK%2F%2B0I%2BtaUgbudUxA%2B536asYsLWVfKa%2BhVnND4NyB1fwjO3bJLf667Yy8Q5jB6TX2HR3Qrq8LCw4Dujjqyaqc1rGLUwdQHHM8iY%2BUDMxLRSiIntldeBz90au3REAmOgZ0RtdnfeSBK3KXa0%2FA9snq37kKL6zL4bZ3EAS%2F2oYsLHTsIOxroXBFP6oz%2BA%3D%3D&traceId=213e032e17650447230113327e0c4f&union_lens=lensId%3APUB%401765044691%400b521400_0da4_19af4dca231_da77%400290EZjuZ2xbgOlH3NTYAyI%40eyJmbG9vcklkIjoyMDg3MTcxMDEwLCJzcG1CIjoiiX3BvcnRhbF92Ml90b29sX3NlbGVjdGlvbmxpc3RfZGV0YWlsX2luZGV4X2h0bSIsInNyY0Zsb29ySWQiiOiiI4MDY2NCJ9",
|
||||||
|
"icon": "/icons/rpi.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mini-pc",
|
||||||
|
"title": "迷你主机",
|
||||||
|
"subtitle": "具有性价比的 X86 小主机",
|
||||||
|
"href": "https://s.click.taobao.com/t?e=m%3D2%26s%3DCb1Quo%2F0Wb5w4vFB6t2Z2ueEDrYVVa64YUrQeSeIhnK53hKxp7mNFuhCZFSw9yq2woqx0EHqItb0JlhLk0Jl4eVWmNyHytKdTKJ80yOOA0Mtd0e5Ku7CIDk5ffgV5fRI1qrpxiwMoCNxc1AtbZGVS936JshS%2F%2FIdwgQgG5hIzyLNEPXytV9ALtCLThlbPuuZLb93Df8fOziGg7JVKZos%2BjLJJgRsfWlWtajntMnDLEHhuApO%2F8klJ62RdK%2B6%2FP7Z6k6s3rDgsC6jO9AJYjY8CXJ%2BwEVkOqHFPSJJXArlDuzso5nyPLY4NPiLqets6lIJ",
|
||||||
|
"icon": "/icons/mini-pc.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gpu",
|
||||||
|
"title": "GPU 显卡",
|
||||||
|
"subtitle": "AI 时代的算力基础设施",
|
||||||
|
"href": "https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BATUJK1olXwEDUFZVCkkRC18IGloWWwALV19aAUonRzBQRQQlBENHFRxWFlVPRjtUBABAQlRcCEBdCUoUBWkBGFoSVAcdDRsBVXsQB29pbiZRKGMHKlclCEgVWghcGV91UQoyVW5dCUoWAmYPHl4RbTYCU24fZp-esLulkIK0yd6n5V1eOEonA2kPH1oQWQUFV19VCnsQA2Y4El0dWQ4LU1hBC00SCmcJK2slXjYFVFdJDjlWUXsOaWslXTYBZF5cCEoQAGoLH1kRQQYHV1lbAFcXBWgMGl4RXgUKUllYOEkWAmsBK2vL05J3IB9Uajl8UStqSz4dFQ9jitDJGTlnA2sPH0olImF5ECFaSU0VVAdNQDxuW09KFiw8SyljbW1jcih3JWNxNlwaS0hqWBhoX2sQbQEEVW4",
|
||||||
|
"icon": "/icons/gpu.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "software",
|
||||||
|
"title": "软件",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "3x-ui",
|
||||||
|
"title": "3X-UI",
|
||||||
|
"subtitle": "科学上网仪表盘",
|
||||||
|
"href": "https://github.com/MHSanaei/3x-ui",
|
||||||
|
"icon": "/icons/3x-ui.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mihomo",
|
||||||
|
"title": "虚空终端 mihomo",
|
||||||
|
"subtitle": "代理软件以及配置 wiki",
|
||||||
|
"href": "https://wiki.metacubex.one/config/",
|
||||||
|
"icon": "/icons/mihomo.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "frp",
|
||||||
|
"title": "frp",
|
||||||
|
"subtitle": "高性能内网穿透",
|
||||||
|
"href": "https://gofrp.org/zh-cn/docs/",
|
||||||
|
"icon": "/icons/frp.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wireguard",
|
||||||
|
"title": "wireguard",
|
||||||
|
"subtitle": "P2P 打洞组网",
|
||||||
|
"href": "https://github.com/WireGuard/wireguard-go",
|
||||||
|
"icon": "/icons/wireguard.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lineageos",
|
||||||
|
"title": "lineageos (pi5)",
|
||||||
|
"subtitle": "树莓派的安卓 LineageOS",
|
||||||
|
"href": "https://konstakang.com/devices/rpi5/",
|
||||||
|
"icon": "/icons/lineageos.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gear",
|
||||||
|
"title": "数码装备",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "ai-glasses",
|
||||||
|
"title": "AI 眼镜",
|
||||||
|
"subtitle": "第一视角拍摄",
|
||||||
|
"href": "https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BATkJK1olXw8AXVxfCU8UA18IGloVWwcKXFlZDUgnRzBQRQQlBENHFRxWFlVPRjtUBABAQlRcCEBdCUoXBW4AE1wRWAUdDRsBVXtwXw1pZzNKAmZgFFwlYUh8UzVBZyZlUQoyVW5dCUoWAmYPHl4RbTYCU24fZpO9hbeBtYKjxt-jwInikZ-fjV8JK1sTWgIDUVtVAEkVA2w4HFscbQ8EXFpVAUwRH2wOHlIdXDYyZF1tD0seF2l6WgkBW3QyZF5tC3sXAm8AGFsVXAEBXEJdDUMVB24UG10SWQcHUVZbAE8fCl8KGloRVDYyitPtY0JrQAYIYj0dXGZKMVc5T5Was35jYlMVXQUTZAQOch9SZTMNbC9gIGdQJi5VCzkRSCpfWDUXVAJlDQoCbw1gcG9qRwYdHnYyUW5aDEgn",
|
||||||
|
"icon": "/icons/ai-glasses.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "action-cam",
|
||||||
|
"title": "运动相机",
|
||||||
|
"subtitle": "骑行记录仪",
|
||||||
|
"href": "https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BASgJK1olXDYCVV9eCE0UB28ME1slGVlaCgFtUQ5SQi0DBUVNGFJeSwUIFxlJX3EIGloWXQABUF5ZAEsIWipURmt3CFhXIQkpbChuRy8JGDpRJUJQDwkbBEcnAl8IGloUXA8FUVtZOHsXBF9edVsUXAcDVV1fDEonAl8IHVwRXAMGVVpaDEsUM2gIEmscWw4GXFdaDlcUBWoBE1olbTYBZFldAV8RcS5aD11nbTYCZF1tCEoXAmsNHl8UXQQeVF1ZDE0UH28OHF8UWAIDUVddCkgnAW4JH1IlbdiMwDxcaAJfBTlNTRpCBHpHLhyDht8GcR8BHFIUTDZ6NSMpYRJweWtBfAUXC2JyXBkpfTNRWi1mGV4UAlgENFpeDA13cRRcTAJLbQMyU1peOA",
|
||||||
|
"icon": "/icons/action-cam.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "drone",
|
||||||
|
"title": "无人机",
|
||||||
|
"subtitle": "天空视角",
|
||||||
|
"href": "https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BASgJK1olXDYCVV9eAUIeB2YMH14lGVlaCgFtUQ5SQi0DBUVNGFJeSwUIFxlJX3EIGloWVA8LUFdZDE4IWipURmtCHHVEABsFVChPYRJAbVhlGRhbFRgLBEcnAl8IGloUXA8FUVtZOHsXBF9edVsUXAcDVV1fDEonAl8IHVwRXAMGVVdbC04VM2gIEmscWw4GXFdaDlcUBWoBE1olbTYBZFldAV8RcS5aD11nbTYCZF1tCEoXAmgLHlgRXgIeVFpfDU0UH28OHF8UWAIDUlteAUwnAW4JH1IlbdiMwCQvcipMBxJhfV9HGgF3FVaDht8GfxgBGVkRTDZUCjsVbzBTYRJbeRkWGG9WAx02D0JcUy5mGRNRFEJiUwgldStzfx1wTj1GbQMyU1peOA",
|
||||||
|
"icon": "/icons/drone.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "overseas",
|
||||||
|
"title": "出海掘金",
|
||||||
|
"emoji": "◎",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "tiktok",
|
||||||
|
"title": "TikTok",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "tiktok",
|
||||||
|
"title": "TikTok",
|
||||||
|
"subtitle": "全球直播平台",
|
||||||
|
"href": "https://www.tiktok.com/",
|
||||||
|
"icon": "/icons/tiktok.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cloud-phone",
|
||||||
|
"title": "云手机",
|
||||||
|
"subtitle": "用开源硬件打造云手机",
|
||||||
|
"href": "https://www.hackrobot.cn/",
|
||||||
|
"icon": "/icons/cloud-phone.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vcam",
|
||||||
|
"title": "vcam 虚拟摄像头",
|
||||||
|
"subtitle": "Xposed 模块,替换摄像头视频流",
|
||||||
|
"href": "https://pan.baidu.com/s/1NW4GMGA3cxfUQNWZPD9vOg?pwd=live",
|
||||||
|
"icon": "/icons/vcam.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "youtube",
|
||||||
|
"title": "YouTube",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "youtube",
|
||||||
|
"title": "YouTube",
|
||||||
|
"subtitle": "全球最大的视频网站",
|
||||||
|
"href": "https://www.youtube.com/",
|
||||||
|
"icon": "/icons/youtube.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "smmfollows",
|
||||||
|
"title": "smmfollows",
|
||||||
|
"subtitle": "刷粉刷量平台",
|
||||||
|
"href": "https://smmfollows.com/ref/dfugq",
|
||||||
|
"icon": "/icons/smmfollows.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "yt-auto-dismiss",
|
||||||
|
"title": "YouTube Studio Auto Dismiss",
|
||||||
|
"subtitle": "油猴脚本,无人直播辅助",
|
||||||
|
"href": "https://greasyfork.org/zh-CN/scripts/557378-youtube-studio-auto-dismiss",
|
||||||
|
"icon": "/icons/yt-auto-dismiss.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "web",
|
||||||
|
"title": "Web 网站",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "namesilo",
|
||||||
|
"title": "namesilo",
|
||||||
|
"subtitle": "注册域名",
|
||||||
|
"href": "https://www.namesilo.com/?rid=3bd4047mc",
|
||||||
|
"icon": "/icons/namesilo.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aapanel",
|
||||||
|
"title": "aapanel",
|
||||||
|
"subtitle": "宝塔面板",
|
||||||
|
"href": "https://www.aapanel.com?referral_code=wcn04vez",
|
||||||
|
"icon": "/icons/aapanel.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cloudflare",
|
||||||
|
"title": "Cloudflare",
|
||||||
|
"subtitle": "互联网基础节点",
|
||||||
|
"href": "https://www.cloudflare.com/",
|
||||||
|
"icon": "/icons/cloudflare.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ga",
|
||||||
|
"title": "Google Analytics",
|
||||||
|
"subtitle": "网站流量统计服务",
|
||||||
|
"href": "https://analytics.google.com/",
|
||||||
|
"icon": "/icons/ga.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "adsense",
|
||||||
|
"title": "Google AdSense",
|
||||||
|
"subtitle": "Google 广告服务",
|
||||||
|
"href": "https://www.google.com/adsense/",
|
||||||
|
"icon": "/icons/adsense.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "live",
|
||||||
|
"title": "无人直播",
|
||||||
|
"emoji": "▷",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "live-soft",
|
||||||
|
"title": "软件",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "obs",
|
||||||
|
"title": "OBS",
|
||||||
|
"subtitle": "免费开源:录屏 / 推流直播",
|
||||||
|
"href": "https://pan.quark.cn/s/03e89f1bbc9b",
|
||||||
|
"icon": "/icons/obs.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ffmpeg",
|
||||||
|
"title": "ffmpeg",
|
||||||
|
"subtitle": "命令行视频处理 / 推流,Python 自动化",
|
||||||
|
"href": "https://www.ffmpeg.org/download.html",
|
||||||
|
"icon": "/icons/ffmpeg.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "srs",
|
||||||
|
"title": "SRS",
|
||||||
|
"subtitle": "开源直播服务器,拉流推流中转",
|
||||||
|
"href": "https://ossrs.io/",
|
||||||
|
"icon": "/icons/srs.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pay",
|
||||||
|
"title": "支付网关",
|
||||||
|
"emoji": "◇",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "merchant",
|
||||||
|
"title": "小微商户",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "nowpayments",
|
||||||
|
"title": "NOWPayments",
|
||||||
|
"subtitle": "加密货币收款",
|
||||||
|
"href": "https://account.nowpayments.io/create-account?link_id=3680795921",
|
||||||
|
"icon": "/icons/nowpayments.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "creem",
|
||||||
|
"title": "Creem",
|
||||||
|
"subtitle": "软件出海收款,税务合规",
|
||||||
|
"href": "https://www.creem.io/",
|
||||||
|
"icon": "/icons/creem.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stripe",
|
||||||
|
"title": "Stripe",
|
||||||
|
"subtitle": "全球在线支付",
|
||||||
|
"href": "https://stripe.com/",
|
||||||
|
"icon": "/icons/stripe.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "zpay",
|
||||||
|
"title": "z-pay",
|
||||||
|
"subtitle": "支持独角数卡 · 易支付",
|
||||||
|
"href": "https://z-pay.cn/?uid=22119",
|
||||||
|
"icon": "/icons/zpay.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "xorpay",
|
||||||
|
"title": "xorpay",
|
||||||
|
"subtitle": "支持收银台支付",
|
||||||
|
"href": "https://xorpay.com?r=nomadro",
|
||||||
|
"icon": "/icons/xorpay.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "books",
|
||||||
|
"title": "书籍",
|
||||||
|
"emoji": "◈",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "handbook",
|
||||||
|
"title": "手册",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "nomad-book",
|
||||||
|
"title": "数字游民手册",
|
||||||
|
"subtitle": "复制链接后在小红书打开",
|
||||||
|
"href": "https://www.xiaohongshu.com/s/poster?bgimg=https%3A%2F%2Fsns-redskillhub-s1.xhscdn.com%2Fred_app_image%2F1040g4m8325ke1q36l0404a4druqrlvd5valvb80%3Fsign%3Dbc0c41c2e219f6ca904f52176bd17f54%26t%3D6c9a7a85&xhsshare=&appuid=5acc75ba4eacab15c751fda5&apptime=1790527237&deeplink=xhsdiscover%3A%2F%2FminiTool%2F6ab8984da1e3af0015584e92%3Fsource%3Dh5%26page_key%3D28%26xhsMpScreenMode%3Dfull%26share_id%3D5e90e5979b054c8baf8b3db4c8993d79%26share_channel%3Dcopy_link",
|
||||||
|
"icon": "/icons/nomad-book.webp",
|
||||||
|
"action": "copy"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "forum",
|
||||||
|
"title": "论坛社区",
|
||||||
|
"emoji": "◉",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "blog",
|
||||||
|
"title": "博客",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "yidu",
|
||||||
|
"title": "异度世界",
|
||||||
|
"subtitle": "独立博客,Hugo 搭建",
|
||||||
|
"href": "https://blog.hackrobot.cn",
|
||||||
|
"icon": "/icons/yidu.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dsx",
|
||||||
|
"title": "大师兄",
|
||||||
|
"subtitle": "AI 实践",
|
||||||
|
"href": "https://dsx2016.com",
|
||||||
|
"icon": "/icons/dsx.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"brandIcon": "/icons/_brand.webp"
|
||||||
|
}
|
||||||
BIN
weapp/icons/1psim.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
weapp/icons/3x-ui.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
weapp/icons/9esim.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
weapp/icons/_brand.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
weapp/icons/aapanel.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
weapp/icons/action-cam.webp
Normal file
|
After Width: | Height: | Size: 794 B |
BIN
weapp/icons/adsense.webp
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
weapp/icons/ai-glasses.webp
Normal file
|
After Width: | Height: | Size: 682 B |