6767 主页加静态子页 (Add a Static Subpage to the 6767 Homepage)
目标
给 6767 主页(6767.chat)新增一个子页面(如 /ai-tools),视觉与主站一致、带图标、走 https。2026-09-24 以「AI 工具集」落地。
关键事实(结构)
- 主页静态根目录:
/home/ubuntu/workspace/static/6767-home/(nginxlocation / { root ...; index index.html; },见/etc/nginx/platform/sites/6767.chat.conf)。 - 子页 = 根目录下建
<name>/index.html即可,nginx 自动服务所有子路径,无需改 routes.json / deploy.sh / 任何 nginx 配置(除非要独立路由/鉴权)。 - 子页复用主站设计系统:
<link rel="stylesheet" href="/assets/apple-site.css">+<script src="/assets/apple-site.js">。主题切换(localStorage.apple_theme+data-theme)、滚动进度条、顶部公告条(/api/notice)、在线人数(/api/visits)全部自动生效。 - 导航栏复制主站
index.html里的.apple-nav,页脚复制.apple-footer;子页锚点链接改成/#about形式回跳主站区块。 - 目录属主
www-data,mkdir/cp需要sudo,写完后sudo chown -R www-data:www-data。 - 新页记得补进
sitemap.xml(robots.txt 全放行,仅禁/api/,无需改)。
图标获取(自托管)
https://cdn.simpleicons.org/<slug>:anthropic、googlegemini 等可用;openai 已被 simple-icons 下架(返回空),OpenAI/Codex 的标从openai/codex仓库内codex-rs/skills/src/assets/samples/openai-docs/assets/openai-small.svg(fill=currentColor,<img>下即黑色)拿。- 官网 favicon:
curl -L https://<site>/favicon.ico(ccswitch.io、bigmodel.cn 可用)。 - GitHub 品牌资源:opencode 官方 logo 在
sst/opencode的packages/console/app/src/asset/brand/(dark/light + square 变体)。 - 应用图标:从本地资产库克隆的仓库直接 copy(如 rikkahub 的
docs/icon.png)。 - 统一放到子页
icons/下,卡片用圆角磁贴,object-fit: contain。
图标磁贴的明暗主题处理(2026-09-24 踩过一遍)
深色磁贴 + 图标分三类,不能一刀切:
- 透明底彩色标(cc-switch 橙、claude-code 橙、codex 蓝、gemini 紫):深色磁贴上直接可见,不改。
- 透明底深色单色标(Anthropic 的 A 字标
claude.svg):深色磁贴上糊掉,需filter: brightness(0) invert(1)反白。 - 自带底色的图标(rikkahub、bigmodel 的 Z、OpenMinis、Antigravity、opencode):本身就是不透明带底的方形/圆形图标,既不反色、也不套磁贴——直接让它充满整个 52px 槽位(
background: transparent+img { width:52px; height:52px }),图标自己就是磁贴,和别的卡片磁贴同尺寸对齐。
判断依据:PNG 四角 alpha 是否透明。四角透明 → 分彩色/单色(单色要反白);四角不透明(自带底)→ 去掉磁贴。踩过的坑:给自带底的图标做 invert 会变成纯白块(rikkahub / bigmodel 都中了),别只看文件名猜(opencode 的「dark」变体其实带浅色底)。
另外:拿官方图标优先抓站点 favicon / apple-touch-icon(如 https://opencode.ai/favicon-96x96-v3.png),比从仓库 brand 目录拼 SVG 变体靠谱——仓库里的 square 变体常带浅色底、和官网观感对不上。
步骤
sudo mkdir -p /home/ubuntu/workspace/static/6767-home/<name>/icons- 写
index.html(复用 apple-site 结构),scp 上去后sudo cp+sudo chown www-data:www-data - 图标抓取/上传同目录,同样 chown
- 主站
index.html导航加<li><a href="/<name>">…</a></li>(若尚未有链接) - sitemap.xml 加
<url>
验证
curl -s -k -o /dev/null -w "%{http_code}\n" https://6767.chat/<name>/ # 期望 200
curl -s -k -o /dev/null -w "%{http_code}\n" https://6767.chat/<name> # 期望 301 → /<name>/
curl -s -k https://6767.chat/<name>/ | grep -c "<卡片类名" # 内容数量
# 逐个图标
for f in icons/*; do curl -s -k -o /dev/null -w "$f %{http_code}\n" https://6767.chat/<name>/$f; done常见坑
- 直接用
mkdir不带 sudo →Permission denied(目录归 www-data)。 /assets/...与/config.js是绝对路径,任何子页都能复用,别在子页里写相对路径引用主站资源。- 图标放进主站
/assets/也行,但子页自带的资源放子页icons/,隔离更干净、升级主站不牵连。 openai-mark.svg用fill="currentColor",直接<img src>渲染为黑色,浅色磁贴下可见,别期望它是彩色。
独立访问统计实例(visit API)
给子页做「与主站隔离」的访问统计(2026-09-24 给 /ai-tools 落地):
- home-api 的访问统计已重构为工厂:
lib/site-visits.js(createVisitTracker(dataFile))与lib/visit-regions.js(createRegionTracker(dataFile))都导出工厂,server.js 里createVisitTracker(path.join(__dirname, "data", "aitools-visits.json"))即拿到独立实例,数据落独立 JSON(aitools-visits.json/aitools-regions.json)。 - 新实例 = 4 个端点(镜像
/api/visits系列):GET /api/ai-tools/visits(打点,返回 total/today/online/location)、/stats(含 30 天 trend + countries/provinces 分布)、POST /heartbeat、POST /leave。 - 前端切换:
apple-site.js的访问打点 URL 现在由window.SITE_CONFIG.visitsBase控制(默认'/api/visits',主站行为不变)。子页在 apple-site.js 加载前设window.SITE_CONFIG.visitsBase = '/api/ai-tools/visits'即打点走新实例、不污染主站计数。 - nginx 410 坑:公网
/api/*默认命中location ^~ /api/ { return 410; }(历史接口下线)。新 API 路径必须去routes.json加 proxy 规则(无斜杠版 →location = ...,带斜杠版 →location ^~ .../)再./deploy.sh,否则公网 410(本地 127.0.0.1 直连正常,极易误判成代码没生效)。 - 改完
pm2 restart home-api;routes.json 改动按 AGENTS.md 提交 platform git 仓库(git add nginx/routes.json nginx/sites/6767.chat.conf)。
卡片网格换行修复(auto-fill → auto-fit)
- 主页
.projects-grid、.connections-grid、ai-tools 的.ai-grid原用repeat(auto-fill, minmax(Npx, 1fr)),最后一行卡片不满格时右侧留空洞、两边不对齐。 auto-fit折叠空轨道、该行卡片自动向左右拉伸占满整行,两边对齐。这是「最外层卡片自适应往左右扩」的标准解法。- 主站静态资源改后要 bump 版本号:
index.html里/assets/apple-site.css?v=20260917这类查询串改成新日期,否则浏览器命中缓存不刷新。
特例:hero 内 AI Launcher 卡片(面板宽度固定,auto-fit 不够用)
.ai-launcher-items在 hero 的 macOS 窗口mockup 里,面板宽度由.macos-content(300px 1fr)决定,最宽也只有 ~630px;两列时每张卡仅 ~260px,容不下图标 + 标题 + URL(约需 265px),于是标题换行。- 这种「容器宽度受限、内容有硬性最小需求」的网格,
minmax的阈值不好掐(正好卡在临界会来回翻)。改用显式断点更稳:默认1fr 1fr,@media (max-width: 1040px)降为单列(单列全宽,和下方 CLI 命令列表的竖排也统一)。 - 再加双保险:标题
white-space: nowrap(永不换行),URLwhite-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0(空间不够时截断次要的 URL 而不是标题)。
带鉴权的子页 + 动态后端(2026-09-24 以 /stars 落地)
/stars(GitHub 星标收藏索引)比 /ai-tools 复杂一档:内容来自后端接口,另有一个只有站长能进的设置页。四个新知识点:
1. 静态子页本身仍然不用改 nginx
/stars/ 由主页静态根的 location / 直接服务,照旧 sudo mkdir + sudo cp 即可。只有需要鉴权的那部分才动 routes.json。
2. 给子页的某条路径加鉴权(auth-api)
/stars/settings/ 要登录才可见,就在 routes.json 里加静态 + auth_request 条目(形状照抄 /agy/):
{
"serverName": "6767.chat www.6767.chat",
"path": "/stars/settings/",
"kind": "static",
"target": "/home/ubuntu/workspace/static/6767-home/stars/settings/",
"owner": "stars",
"auth": "auth-api via Nginx auth_request",
"authRequest": true,
"authRedirect": "api",
"status": "active",
"retiredCode": null
}authRequest: true+authRedirect: "api"→ nginx 发auth_request /_auth_verify(打 auth-api 的/internal/auth/verify),401 时error_page 401 = @api_login跳登录页并带?next=。- 代理型管理接口(
/api/star-lists/admin*)用authRequest: true+authUserHeader: true,不加authRedirect(让 401 原样返回给前端 fetch 处理)。authUserHeader把X-Auth-User塞给上游,后端只认这个头:有 = 已登录,没 = 401,不用自己写登录逻辑。 - 无斜杠版另加一条
kind: "redirect"→ 301 到带斜杠版,省得依赖 nginx 对目录的隐式 301。
3. 动态数据后端:抓上游 + 只读缓存
仓库数据来自 home-api 的 lib/github-star-lists.js(照已下线的 zhihu-immersive-reader 同步模块骨架写):
- 定时(6h)经
curl -x http://127.0.0.1:7890打 GitHub GraphQL,结果原子写data/github-star-lists.json。 - 对外接口只读缓存,请求路径上绝不打上游——否则有人刷页面就撞限流。
- 服务器上没装 undici,Node 内置
fetch不认HTTPS_PROXY,所以走代理只能 spawncurl(零依赖;这类同步几小时才跑一次,开销无所谓)。
4. GitHub Lists 的权限真相(踩过)
想从 GitHub 星标里读「分组(Lists)」,只有一条路:
- REST 没有:
GET /users/{user}/lists→ 404。只有 GraphQL。 - 读公开 list 不需要任何 scope:一张只有
gist/read:org/repo的票就能读user(login:"X"){lists{...}}。写才要userscope ——createUserList/updateUserListsForItem会报INSUFFICIENT_SCOPES。 - 补
userscope 只能gh auth refresh -h github.com -s user,且必须在浏览器点同意;重新gh auth login只会拿回默认 scopes,补不上。 - 组名 URL slug 规则:转小写、去掉
&、空格转-、中文原样保留。
5. 列表类接口的首屏性能(2026-09-24 优化 /stars)
253 条装修齐字段时是 268 KB(gzip 114 KB),且响应头是 no-store —— 每次访问全量重下。四条改完降到 728 B:
- 索引 / 明细分两级:
GET /api/star-lists只发分组元数据(组名、slug、数量、置顶),GET /api/star-lists/items?list=<slug>发某一组明细,不带list发全部。前端首屏只拉索引,明细按组懒加载 + 内存缓存。 - 别用
no-store:数据几小时才变一次,public, max-age=300足够;Express 的 ETag 配合 revalidate 还能直接 304。 - 砍掉前端不渲染的字段:
owner/name(由repo拆)、homepage/forks/updatedAt一律不发;描述截到 320 字,topics 截到 6 个。 - nginx 已开 gzip,所以后端不用再加
compression中间件。
6. 卡片被畸形描述撑变形的三件套
网格卡片渲染外部数据(仓库描述、标题)时,三个一起加才稳:
.grid > * { min-width: 0; } /* 网格项默认 min-width:auto,会被内容撑破轨道 */
.grid .name { overflow-wrap: anywhere; } /* 超长无空格串(URL、连续英文)允许断 */
.grid .desc { /* 固定行数截断 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
overflow-wrap: anywhere;
}只加 -webkit-line-clamp 不够——没有 min-width: 0,长串仍会把整列撑宽。
7. 列表切换不要 scrollTo(0)
列表页的内容区常在页面中部(上面有 hero),切分组时无条件 scrollTo({top: 0}) 会把人甩回 hero,很晕。只在「用户已经滚到内容区下方」时才拉回内容区顶部:
function settleScroll() {
var top = layout.getBoundingClientRect().top + window.pageYOffset - 76;
if (window.pageYOffset > top + 24) window.scrollTo({ top, behavior: "smooth" });
}配合 .stars-layout { scroll-margin-top: 84px } 让锚点/滚动定位避开吸顶导航。
适用边界
- 纯展示性子页:只做第 1 条即可,无需 routes.json。
- 需要独立 API / 鉴权 / 服务进程:按上面 2–3 条走 routes.json +
deploy.sh,数据侧照「抓上游 + 只读缓存」写模块。 - 视觉一致性依赖
apple-site.css的设计令牌(--text-primary等),子页自定义样式优先用var(--...),主题自动适配。 - 另见 tx-nginx-routes-json(routes.json 是唯一真源)与 platform 仓库规范。
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web