tamakyi
|
4e574ac64a
|
feat(insights): 箱主数据面板——访问量、提问趋势、处理漏斗
新增 /user/insights,让箱主第一次能看到「有多少人来过我的箱、提问处理得怎么样」。
数据来源:
- 新表 box_views:一行 = 一访客×一天×一箱,views 累加,唯一索引
(user_id, viewed_on, visitor)。刻意不内嵌 dbutil.Model —— 软删除 + 唯一索引
会让被删行永久占槽,且删过的槽位再也插不进去。
- 访客标识 = HMAC(日盐, IP) 前 32 hex:同日可去重(UV 准)、跨日不可关联(隐私)。
代价是近 7/30 天 UV = 各日 UV 之和,页面上已写明口径。
写入是异步的:buffered channel 512 + 2s 批量 flush,队列满即丢(统计尽力而为,
不该因为记一笔访问就拖慢页面)。过滤箱主本人、空 UA、爬虫 UA、预取请求。
密钥复用 server.xsrf_key;每日清理 400 天前的明细。
页面(纯 CSS 画图,不引图表库):
- 今日/近 7 天/近 30 天 KPI + 上周对比
- 30 天 PV/UV 双柱趋势、24 小时时段分布
- 提问处理漏斗 + 回答率进度条 + 自动结论
聚合层 internal/db/box_analytics.go 复用 analyticsDialectFor 的时区处理,
与全站 stats 分开(那个不含单箱维度、按自然日缓存)。
测试:box_views 4 项(含「同批同键先内存合并」——PG 的 ON CONFLICT 不允许
同条 INSERT 二次命中同一行)、insights 的 pct/窗口切分/visitorID/爬虫过滤。
|
2026-10-29 18:25:53 +08:00 |
|
tamakyi
|
5def41d0f8
|
feat(ux): 真 404 / HTML 500 页,以及「提问者找回自己的提问」
一、错误页
原来未知路径一律 302 到首页:用户点了个失效链接,地址栏莫名跳到首页却什么
提示都没有,分不清是链接坏了还是自己点错了;对搜索引擎是软 404,失效链接会
被当成有效页面一直占抓取预算。500 则是 flamego.Recovery() 的纯文本
"Internal Server Error",白底黑字两行英文,没有返回入口,也没法报编号。
现在是 internal/route/errorpage.go 里的 writeErrorPage:
- 404 保留状态码 + 一页能看懂的说明 + 返回首页按钮 + noindex
- panic 由自写的 recoveryMiddleware 兜住,渲染同样的 HTML 500 页,
并带上 trace id(用户反馈时报这个号就能定位日志),日志里也带完整栈
- 响应已写出时不再写头(否则 net/http 会忽略状态码并打 superfluous 警告)
为什么不套模板:f.NotFound 不经过组级中间件 —— template.Templater 与
Contexter(连同 session、CSRF)都挂在 f.Group 上,在 NotFound 里注入
template.Data 会直接 panic(实测:"value not found for type template.Data")。
而 recovery 是最外层,那时模板还没装配。错误页恰恰最不该依赖可能出错的链路,
所以用内联 HTML + i18n.T,语言从 Accept-Language 猜(拿不到 session)。
蜜罐分流仍在 NotFound 最前面:临时打开 enable_honeypot 实测 /wp-admin
仍返回假 WordPress 登录页(200),普通未知路径返回 404。
二、提问者找回自己的提问
不填邮箱时,唯一能追踪这条提问的东西是提问成功页上那条链接 —— 关掉页面、
链接冲掉了就永久失联。箱主认真答了,提问者永远不知道。这是这类产品最核心的
体验断点,而它不需要账号体系就能解决:
- 成功页把 {domain, id, token} 写进本机 localStorage(最多 30 条,按 domain+id 去重)
- 再访该箱时显示一条不打扰的横条:「这台设备上有 N 条提问记录,其中 M 条已有回答」,
点「查看」展开列表,每条可直接打开
- 新增 POST /api/v1/ask-status 批量回答「哪些已被回答」:一次 IN 查询,不逐条 N+1
它不能用来枚举:必须同时给出正确 token 才返回 ok=true,而「token 错」「id 不存在」
「不属于该箱主」三种情况的响应完全相同(ok=false / answered=false / excerpt 为空),
无法区分。token 比较走常数时间,单次最多 20 条,限流 30/分钟。用 POST 不用 GET,
因为 token 不能进 URL(会留在浏览器历史、代理日志、Referer 里)。
列表摘要先过 MarkdownPlainText 再按 rune 截断:直接截原文会露出 
这种噪音,一条带图的提问几乎只剩链接。
验证:
- verify_ask_history.py 12/12(含「错误 token / 不存在的 id / 别人的提问」三者
响应完全一致、缺 CSRF 被拒、超 20 条截断)
- verify_ask_history_ui.mjs 用 CDP 在真实 Chrome 里跑:无记录时提示条隐藏;
注入记录后显示「1 条,其中 1 条已有回答」;列表渲染、链接带 token、点击展开正常
- 错误页单测 6 项(HTML/不泄露 panic 内容/已写出时不重复写头/语言选择/状态码/转义)
|
2026-10-29 17:18:39 +08:00 |
|
tamakyi
|
03cb5ddcf0
|
fix(ui): 提交防重复覆盖验证码路径、waline 按页加载、懒加载、safe-area、硬编码文案接字典
1) 防重复点击原来漏了最主要的一条路径
footer 的全局 submit 监听会禁用按钮,但**程序化 form.submit() 不触发 submit 事件** ——
走 reCAPTCHA / Turnstile 的流程是在回调里调 form.submit() 的,而默认配置
captcha_type = recaptcha,也就是主提问流一直没有防重复。登录、注册、找回密码、
投票、注销账号同样走这个回调。
→ 把「切提交中状态」抽成 tbBeginSubmit(form, trigger) 挂到 window,
验证码回调先调它再 submit。7 个 data-callback="onSubmit" 的模板全部受益。
顺带加了 data-tb-restore 的重复覆盖保护,避免 bfcache 复位后按钮卡在转圈。
2) waline.js / waline.css 从全站改按页加载
只有 home.html 与 item.html 用评论区,但此前挂在 header 里 —— 登录、后台、投票、
私聊每页都下载解析 waline.js(2.15 未压缩约 200KB)。实测 /login 已无引用。
3) 图片懒加载:explore 列表头像与题目配图加 loading="lazy"(首屏推荐位不加,避免影响 LCP)。
4) 移动端底栏:body 只预留固定 56px,而底栏 padding 含 safe-area-inset-bottom。
带 Home Indicator 的 iPhone 上 inset 约 34px、底栏实际约 88px,最后约 32px
(页脚备案、存活计时)被压在栏下,滚到底也看不到。改成 calc(56px + env(...))。
5) 硬编码中文接字典(en/ja 用户此前直接看到中文):
history-template 的「查看回答 / 加载更多 / 加载中... / 无更多留言」、
单题页「@xxx 以前回答过的问题」(字典里本来就有 ask.history)、
投票页「(最多 N 项)」改为带参数的整句(原先前半走字典、后半手写中文)。
新增 4 个 key × 4 语言。
实测:箱主语言设为 en 后箱主页显示 View answer ×2 / Load more / No more posts,中文零残留。
|
2026-10-29 16:28:53 +08:00 |
|
tamakyi
|
7802bb2078
|
fix(security): 修复安全审计的 P1/P2/P3 项,含全局复查新发现的两处越权与外跳
P1:
- 外链图片:markdown 渲染层只放行站内与自有 CDN 图片。配置里的
image_bucket_cdn_host 常写成 host/path,统一归一化后再比对,避免把自家图一起剥掉
- 密码:页面/提问密码最小 6 位;密码比对挪到验证码之后;页面密码失败锁定 5 次/10 分钟
- 客户端 IP:CF-Connecting-IP / EO-Client-IP 只在 TCP 对端可信(回环或 trusted_proxies)
时采信,公网对端带来的权威头一律忽略
P2/P3:
- 跳转参数收敛到 shared.SafeRedirectTarget:拒反斜杠与控制字符(url.Parse 会把 %0d%0a
解成 CRLF,伪造 Location),最后再用 path.Clean 验一次前缀
- 注册不再暴露「邮箱已注册 / 已停用」:与全新注册共用同一个成功出口,只在日志留痕
- CSP:默认 enforce,白名单由配置派生,覆盖 umami(运行时注入)、Turnstile 与
按 sentry_dsn 条件放行的 Sentry CDN
- /user 与 /_ 下发 no-store;补 X-XSS-Protection、Permissions-Policy,并把安全头前移到
静态资源中间件之前(原先静态文件拿不到 nosniff/HSTS)
- 封禁事实落库 ban_records;429 统一带 Retry-After 与本地化文案
- token 比较改常量时间;探索页页码上限;JSON 响应显式 Content-Type
全局复查补的两处:
- 私聊 SendMessage 的 POST 缺 conv.OwnerID 校验,被拉黑者可换 domain 继续发消息
- 页面密码 next 与登录 to 同源的反斜杠外跳
验收:verify_p1_fixes 4/4、verify_round2 19/19、verify_page_next 6/6、
verify_chat_owner 3/3(含变异对照);单测与 go build ./... 全通过。
|
2026-10-27 12:37:40 +08:00 |
|
tamakyi
|
a3cf99a8da
|
fix(stats): 环比句点明月份与条数,不再写「上月…环比 -75%」
「上月提问量环比 -75.0%」这句要读者做两次翻译:先把「上月」翻成具体月份,
再把「环比」翻成「比上上月」。两个相对词叠在一起,站长本人看到就以为
是「上月同期 vs 本月同期」—— 而它其实比的是两个完整自然月。
改前:上月提问量环比 -75.0%。
改后:9 月收到 5,620 条提问,比 8 月的 4,890 条多了 14.9%。
口径一个字没改(仍是上月 vs 上上月,本月没过完不参与),只是把月份和条数
写进句子:读者不需要知道「环比」的定义也能判断这句话在说什么,还能顺手
核对百分比(5620/4890 确实是 +14.9%)。
实现:
- `monthText` 按语言出月份名 —— CJK 一律「9 月」,英文要 "September"
("month 9" 不是英语的说法)。不带年份:相比的两个月相邻。
- 一个词条拆成 momUp / momDown / momFlat:方向词塞不进同一个模板,
四种语言里「多了 / 少了」的位置和词形都不一样。
- `|pct| < 0.05` 走持平,否则会出现「多了 0.0%」。
测试:`TestInsightMoMNamesBothMonths` 锁住「句子里同时有两个月份名和两个条数」,
覆盖上升/下降/持平;`TestMonthText` 锁语言差异。
顺手修预览数据:`zz_preview_test.go` 的 `GrowthMoM` 原来写死 12.3,和它造的
monthly 对不上。以前句子里只有百分比无所谓,现在会连条数一起印出来,
截图就成了「5,620 比 4,890 多了 12.3%」这种假证据,改成从数据算。
|
2026-10-25 09:27:57 +08:00 |
|
tamakyi
|
79199ac3cd
|
fix(stats): 删掉「没有推测」这类对站长说的话,文案统一以访客为读者
「数据速览」下面那句「下面几句是从上面的数字直接读出来的,没有推测」,
是在向读者解释「我没有编」—— 那是作者视角的免责,不是访客需要的信息。
读者看到「没有推测」只会想:那我该怀疑什么?删掉,卡片留标题 + 结论列表
即可(`stats.insight.title` 已经说明这组是什么)。
顺着把整页 40 余条文案过了一遍,同类的还有页脚:
「累计数字与图表是当天重算一次的快照(当天第一次访问时算,服务重启也会重算)」
→ 「累计数字与图表每天更新一次」
访客不需要知道缓存是懒重算、更不需要知道重启会触发重算;真要精确的新鲜度,
上一行「快照时间」已经给了到秒的值。这条注的职责只是说清「累计按天、今日实时」。
保留的两处,判断过不属于这个问题:
- `stats.foot.privacy` 与顶部 `stats.lead` 后半句近乎同义 —— 页脚隐私声明是惯例,
重复一次不算噪音,删了反而有人以为底部没声明。
- 「(实时)」在 60 秒缓存下仍成立。
删掉说明行后标题与列表之间只剩 4px,给 `.tb-insights` 补 `margin: 8px 0 0`
(原来靠 `tb-card-note` 的 16px 撑开)。顺手修掉模板里一条过期注释 ——
它还写着「四条时间」「哪个是基准日」,上一轮已改成三行「快照时间」。
i18n 四语言同步(有词条数量对等测试兜底);build + i18n/stats/template/templates
测试全绿,中英页脚与数据速览卡片截图确认过间距。
|
2026-10-25 09:18:00 +08:00 |
|
tamakyi
|
af39651734
|
fix(stats): 页脚时间标签改成「快照时间」,去掉重复的「数据基准日」
原来四行时间里,前两行说的是同一件事:
「数据基准日:2026-10-24」+「计算于:2026-10-24 14:05:22」。
`DataDate` 就是 `UpdatedAt` 的日期部分(同一个 now 格式化出来的),
两行并列只是把同一个值拆成粗/细两种粒度。
而且「计算于」没有主语:读者站在页脚最底部看到它,会当成「整页数据
算到这一刻」,可今日两项恰恰不在这份快照里 —— 命中缓存时
`withToday` 会用更新的读数替换它们,页面上就出现「标注 14:05、
今日提问却是 22:35 的数」。
改成三行:
快照时间: 2026-10-24 14:05:22
最后一条提问: ...
最后一条回答: ...
「快照只含累计数字与图表、今日两项实时」由下面那条 cacheNote 说明,
不塞进标签里(标签越短越好扫)。词条同步:快照时间 / 快照時間 /
Snapshot: / 集計時刻。
顺带删掉 `Overview.DataDate` 字段:模板不用它之后就没有读者了,
留着它会诱使以后的人再拼出「基准日 + 计算于」这种重复行。
无 schema/接口变更,纯展示层。i18n 有词条数量对等测试,四个语言已
一起改;`go build ./...` 与 i18n / stats / template / templates /
route 测试全绿。
|
2026-10-24 22:39:47 +08:00 |
|
tamakyi
|
cbb8908275
|
feat(stats): 近 30 天折线补纵轴刻度
原来这条折线只有横轴日期,纵轴没有任何参照:读者看到峰谷不知道自己
在看 3 次还是 300 次的起伏。现在补上三条刻度(上限 / 一半 / 0)加对应
的横向虚线。
纵轴上限取 niceMax(峰值),不是峰值本身。峰值正好压在顶格里时,读者
分不清那到底是确切的数还是被裁掉的数;向上取整到 1/2/1.5/3/4/6/8×10^n
之后顶部留白固定,三条刻度又都是整数和好读的数(1.5 倍只在量级≥100
时参与,否则算出来是 7.5 这种半格)。上限恒为偶数,中间刻度必然是整数。
刻度文字不放进 SVG:viewBox 用 preserveAspectRatio="none" 横向拉伸填满
卡片,里面的文字会跟着被拉扁,窄屏尤其明显。改成左边一列 HTML 标签 +
横向虚线仍在 SVG 里(描边锁 non-scaling-stroke)。列宽靠一份看不见的
「最宽刻度」占位撑开,不写死 em。
同时把 Polyline/AreaPath 收敛成单个 NewLineChart:坐标、面积、刻度必须
共用同一个上限,分两次算就会各按自己看到的峰值归一化,出现刻度和线错开
的图。面积路径改为沿基线闭合,不再漏进轴标签的留白区。
新增 chart_test.go 锁住三条不变式:中间刻度必为整数、上限不离开峰值一倍、
刻度/折线/面积共用上限;另有 0..2000 的穷举检查。
|
2026-10-24 01:57:36 +08:00 |
|
tamakyi
|
64436d9d61
|
fix(stats): 数据速览用完整星期名,脚注时间改逐行对齐
- 「提问最集中在 三 19 点前后」:洞察复用了热力图列头的短标签
stats.wd.*(一…日 / Mon…),那是为窄列头设计的单字词条,
进句子就成了「三」。新增 stats.weekday.1..7 完整名(周一 / Monday /
月曜日),洞察改用该组,stats.wd.* 只留给列头;补测试断言锁住。
- 页脚入口文案「数据」→「统计数据」(四语言同步)。
- 页面脚注四条时间由「基准日 x · 计算于 y」一行改为两列网格逐行排列,
标签统一带冒号,下面两段说明保持独立段落。
|
2026-10-24 01:08:50 +08:00 |
|
tamakyi
|
a5441c99fd
|
feat(stats): 新增公开数据页 /stats,入口收敛到页脚
首页底部那张「站点统计」卡片下线,完整数据移到公开只读页 /stats:
今日实时数、概览六格、近 30 天折线与 12 个月柱状、三个占比环、
7×24 时段热力图、提问字数与回答耗时分布。
- 聚合在 internal/db/analytics.go(SUM(CASE WHEN),方言差异收敛在 analyticsDialectFor)
- 结果按自然日缓存,跨日重算一次后全站共用;重算失败降级返回上一份快照
- 图表零前端依赖:服务端算坐标的 SVG + CSS grid,禁用 JS 页面仍完整
- 环比取「上月 vs 上上月」:本月未过完,否则每月 1 号会得出 -95%
- 入口只在页脚「社区规则」与「找站长」之间;route.Home 不再计算统计
- 补 questions(created_at) 索引;启动后异步预热当天快照
- 测试:analytics 连真库验证软删除不计入、分桶下标、DOW 对齐
|
2026-10-24 00:57:08 +08:00 |
|
tamakyi
|
c680671b7e
|
feat(user/questions): 手机端时间跨年自动补年份,避免省略年份后产生歧义
DateCompact:与当前同一年的渲染「月-日 时:分」,跨年补回完整「年-月-日 时:分」。
只砍年份会让「去年今天」和「今年今天」显示成同一串,逐条补年份比按年分组便宜得多
(列表带游标分页 + 可切排序/筛选,组标题会跨页重复或错位)。
桌面端不受影响,仍显示 Y-m-d H:i:s。
测试覆盖年份边界(同年 1/1 与 12/31、跨年);预览数据里放了一条 2025 年的提问用于肉眼核对。
|
2026-10-23 21:04:50 +08:00 |
|
tamakyi
|
812b70cb4c
|
fix(user/questions): 筛选标签改为「已读 是/否」,手机端条目标签行不再折行
- 筛选栏第 5 列标签「阅读状态」四字,在手机约 53px 的窄列里必然折成两行,
改为「已读」,选项改为 全部/是/否(option value 仍是 read/unread,后端不变)。
词条 qlist.readStatus 由 qlist.readFilter + qlist.filterYes/filterNo 取代,四语言同步。
- 条目行「时间 + 状态标签 + 标记/置顶」在手机上放不下:实测 360 宽可用 306px,
完整时间单条就占 127px,2 标签 + 2 按钮需 343px。手机端时间改为「月-日 时:分」
(tb-short/tb-text 复用状态标签的 640px 断点,桌面端仍显示完整年月日时分秒),
同时把 .qlist-pill 的基础内边距 40/8 收到 34/6(桌面 @media 仍覆盖回 62/10)。
结果:2 标签 + 2 按钮在 360 宽即单行;三标签(私密+待答+未读)在 ≤375 仍会折行。
|
2026-10-23 21:02:37 +08:00 |
|
tamakyi
|
818fa74160
|
fix(honeypot): 取证记精确地址、封禁用 /64 键,并补建封与拦截日志
线上实测:IPv6 访问蜜罐后收到「你的 IP 已被封禁」,邮件里却是
`2408:…::/64` —— 既不像自己的地址,也说不清封了多大范围;同时用户自己
仍能正常访问,看日志也无从判断到底有没有封。
根因是「取证地址」与「封禁键」混用同一个变量(都取 LimitKey 的折叠值):
- 取证丢掉精确 IPv6 后 64 位,事后无法定位到具体主机、没法和其他日志对账;
- 邮件只给聚合键,运维第一反应是「不是我」,排查方向直接被带偏。
改动:
- Serve 拆 clientIP(ResolveClientIP,精确)与 banKey(LimitKey,/64):
DB / 凭据文件 / 告警邮件用前者,封禁 / 探测采样 / 告警去重用后者;
- autoban.Options 新增 OnBan 回调(保持纯标准库、不引入 logrus),
route.newBanner 注入后每次建封打 WARN:ban_key / reason / duration / expires_at;
中间件 403 分支加 Debug 日志(被封者会持续请求,Info 会被刷屏);
- 告警邮件新增「封禁范围」行,仅 ip != ban_key 时渲染(IPv4 不重复显示),
顺带把过时的「已入库」改为「写入取证文件」——凭据早就外置了;
- honeypot stats 的来源排行改按 LimitKey 合并,否则 IPv6 轮换地址会把
排行碎成几十条各 1 次的记录;合并口径与封禁完全一致。
测试(每项都做了变异验证:改坏→确认变红→恢复):
- TestHoneypotRecordsExactIPv6ButBansByPrefix:精确地址进 DB 与凭据文件、
/64 才是封禁键、同 /64 换地址仍 403;
- TestOnBanCallback:建封回调带键/时长/原因,封禁期内重复命中不重复回调;
- TestHoneypotAlertTemplateRenders:IPv6 渲染封禁范围、IPv4 不重复;
- TestMergeSourcesByKey:同 /64 合并、IPv4 原样、limit 与空输入。
|
2026-10-23 10:53:03 +08:00 |
|
tamakyi
|
0181fc5c66
|
fix templates v3
|
2026-10-22 18:26:30 +08:00 |
|
tamakyi
|
b939d26ff0
|
fix templates v2
|
2026-10-22 17:49:25 +08:00 |
|
tamakyi
|
bd02eb7ed2
|
fix templates v1
|
2026-10-22 17:04:10 +08:00 |
|
tamakyi
|
2981731d4f
|
announcement v3
|
2026-10-22 15:28:04 +08:00 |
|
tamakyi
|
e1e840f895
|
announcement v1
|
2026-10-22 15:08:24 +08:00 |
|
tamakyi
|
f37ff28a46
|
提问管理页面板补卡片底:换主题后内容不再裸铺在场景上
现象:只有 /user/questions 这一页的「卡片」像完全透明的,其它后台页正常。
根因不是主题,是模板本来就没有卡片容器:提问列表是
<div class=uk-margin-medium-bottom> + <hr> 直接铺在页面上,整个模板只有 1 处 .uk-card
(还是投票 tab 里的「我的投票」标题条)。默认主题底色是纯白,所以看不出来;
换成带整页插画的主题后,场景整片透上来,就成了「卡片透明」。
给 .qlist-panel 兜一层卡片底(--tb-surface + 圆角 + 阴影),三个 tab 共用;
面板内已有的一张 .uk-card-default 降成 --tb-surface-2 去阴影,避免卡片套卡片。
≤640px 收紧内边距。与主题无关,默认外观也会多这层卡(和其它后台页一致)。
|
2026-10-21 19:32:29 +08:00 |
|
tamakyi
|
1f29d3eb2e
|
修设置页主题色块被模板过滤器替换成 ZgotmplZ
html/template 对 style 属性做 CSS 过滤,linear-gradient(...) 未标记安全会被整体替换成
ZgotmplZ —— 线上设置页四个主题色块一直是灰底(既有问题,不是这次配色改动引入)。
- FuncMap 新增 safeCSS(string → template.CSS);值只来自 internal/theme.swatchOf,
由两个已校验的色值拼成,不接受用户输入,所以这里放开是安全的
- user/profile.html 的色块改 {{ safeCSS .Swatch }}
- 新增 TestThemeSwatchRenders:渲染 user/profile,断言不含 ZgotmplZ 且含渐变串;
已做变异验证(去掉 safeCSS 后该用例失败)
|
2026-10-21 18:53:10 +08:00 |
|
tamakyi
|
27856dd4a2
|
字号阶梯收尾 + 主色补到 WCAG AA(新增 --tb-on-primary)
一号事项(剩余令牌化):
- templates/user/profile.html 20 处、base.css 9 处 font-size 全部落到 --tb-fs-*
- base.css 导航四档(0.95/0.88/0.82/0.78)合并成「桌面 md → ≤1000 xs」;logo 三档统一 lg
- 有理由保留的:.tb-pin-flag / .md-body code 的 0.9em(相对父级)、验证码 1.4/1.2rem(等宽字形)、
chat 关闭按钮 40px、password 页 3em、mail/*(邮件客户端不保证 var());行内 12px/0.75rem 等值顺手换掉
二号事项(主色对比度):
- base/default #1e87f0(3.42) → #0e6fd0(4.70);shiroko #4a7fc1(3.69) → #3b6dac(4.74);
sakura #e5678f(2.80) → #ba3e66(4.70,饱和度压到 .5 保持柔和)。同色相只压亮度,目标 4.7 留余量
- 深色块 muted 之前只对 --tb-bg 算(4.7 以为过了),对压平后的卡片其实 4.31/4.17 → 提到 #a18790 / #7f8ea1
- 新增 --tb-on-primary(亮 #ffffff / 暗 #0b1016):深色模式主色偏亮,白字只有 2.2~3.1:1,
用同一个令牌兼顾「链接色要深」和「按钮底要深」是矛盾的。.uk-button-primary 与
.uk-label:not(语义色) 改吃它 → 深色按钮变成浅底深字(6.10 / 8.65 / 7.27)
- 顺手封掉几处主色泄漏:header 验证码 focus、profile .feat-day.is-on、explore 随机投票的 JS outline、
vote-results 行内色,全部改成令牌(否则换主题时不跟着变)
生成器同步:paletteVars 导出 --tb-on-primary(白字/深字择优);?dump=palette 增加
onPrimaryCr / onPrimaryPickedBest / warnings —— 主色卡中间调时只警告不判失败;
配色面板给橙色提示。_scaffold 令牌 29 → 30。StaticVersion → 20261021c。
验证:TestSwatch 断言同步为 #ba3e66;8 种组合(base 亮暗 + 三主题亮暗)对比度全部 ≥4.5;
go test internal/{theme,templates,conf,i18n} 全绿;生成器 validate/fit/quality/palette/layers/fx/fxpipe 全过。
|
2026-10-21 18:39:08 +08:00 |
|
tamakyi
|
001306d940
|
模板样式令牌化:字号落到 5 档阶梯、圆角改走 --tb-radius
11 个模板里的硬编码字号/圆角换成令牌,为之后的深色与主题一致性打底。
字号(base.css 的 --tb-fs-xs/md/lg/xl/hero = 12/14/16/24/28):
- 0.70~0.88rem、0.72rem 等五六档零碎值 → --tb-fs-xs(标签/meta) / --tb-fs-md(正文、列表、按钮)
- 0.95~1rem → --tb-fs-lg;1.5rem 大数字 → --tb-fs-xl;1.75rem 首页主标题 → --tb-fs-hero
- 以下几处**故意改变了观感**(不是等价替换,先记下来免得以后当 regression 查):
explore 的 .explore-name 1.2rem → 1rem;.hot-vote-title 0.98 → 0.875;
首页 .tb-stat-num 手机端 1.25 → 1rem;.hot-vote-action 0.82 → 0.875(变大)。
圆角:
- 8/9/10px → --tb-radius-sm(8) / --tb-radius(12,对话框与气泡);3/4/5/6px → --tb-radius-xs(6)
- 头像 50%、胶囊 999px 保留原样(语义值,不是视觉随意值)
其它:header 验证码输入框边框 #ccc → var(--tb-border);chat 气泡与图片的圆角走内联 var()。
仍未令牌化(留给下一轮):templates/user/profile.html 还有 16 处 font-size、base.css 自身 11 处
(0.78/0.82/0.88/0.95/1.1),邮件模板 mail/* 保持 px 不动(邮件客户端不保证 var())。
|
2026-10-21 18:28:26 +08:00 |
|
tamakyi
|
79051acae8
|
miguan v1
|
2026-10-21 12:09:28 +08:00 |
|
tamakyi
|
6c014b3ad2
|
修 test01:窄屏装饰全藏导致素材消失、主题名显示成 i18n key
- 生成器:勾「窄屏隐藏全部装饰层」时给橙色警告(几层会消失)、图层行标「窄屏隐藏」、加层与导出结果再提醒;「窄屏」下拉在被全藏覆盖时标注不生效
- 新增 ?narrow=hide 复现该状态(这是「素材在手机上看不到」的头号来源:@media 把 10 槽全写 none,桌面完全正常)
- 主题名:新增 theme.DisplayName + 模板函数 themeName,i18n 未登记 theme.name.<slug> 时回退 slug;原来模板直接调用 i18n.T,缺词条就显示 "theme.name.test01"
- test01.css 删掉 @media 全 none 段:装饰是 fit 单位,浏览器会自己按视口缩,不需要这段
- StaticVersion → 20261020g(主题 css 内容变了,必须换缓存键,否则浏览器还会用旧文件)
|
2026-10-20 18:23:21 +08:00 |
|
tamakyi
|
ddee221de2
|
feat(theme): 主题支持按页面分别配装饰,生成器改用真实页面画布
原来的生成器只能配「背景 + 人物 + 道具」三件套,预览还是自己画的仿制页面 ——
跟真实页面不像,也没法给不同页面配不同内容。
机制层:
- internal/context.pageScope() 按路径归类,输出 <html data-page-scope>:
box / question / admin / profile / vote(首页与 /explore 不加载主题,不列)
- /user/questions 单独归为 admin:题目列表信息密,和设置表单的装饰诉求不同
- base.css 装饰槽位 6 -> 10,背景 8 层 -> 12 层;主题按作用域覆盖时同样写满 10 个
- 新增 TestDecoSlotCount:逐文件逐声明检查 10 个值。少写一个不报错,
只会让图层与 size/position 错位(整页重复同一张图 / 人物跑到左上角),注释守不住
生成器:
- 画板改成 iframe 加载 outputs/preview-*.html(真实模板渲染),配 serve.py 起本地服务
解决 file:// 下父页面读不到 iframe DOM 的问题
- 八个页面作用域(去掉不生效的首页/探索),每个可独立配背景与最多 10 层装饰
- 图层类型:图片 / 径向光斑 / 线性光;9 宫格锚点 + 像素偏移,可在画板上直接拖
- 位置统一写 calc(0%|50%|100% ± Npx):百分比是相对 (容器-图片),
不用 left/bottom 关键字混搭,避免三值语法非法导致整条声明作废
- 配色只填 6 个基础色,其余 4 个必需令牌按方向派生(surface-2 往 text 靠,
浅色变深、深色变亮,方向自动正确)
配套:
- zz_preview_test.go 增加单题页 / 题目列表 / 投票页预览,作为生成器画板
- docs/theme-page-scope.md 记录作用域取值、选择器特异性与 10 槽位约束
- StaticVersion 20261020e(base.css 变了)
验证:生成器导出的 CSS 通过 TestThemeCSSComplete + TestDecoSlotCount;
真实箱主页计算样式为 12 层对齐;临时主题验证「question 覆盖为 none」生效、
未覆盖时回退全局。
|
2026-10-20 10:53:44 +08:00 |
|
tamakyi
|
3202ef9821
|
theme v2
|
2026-10-19 16:16:07 +08:00 |
|
tamakyi
|
e9fba8b9ed
|
theme v1
|
2026-10-19 15:23:11 +08:00 |
|
tamakyi
|
86dc8384bc
|
全局美化 v5
|
2026-10-19 12:44:32 +08:00 |
|
tamakyi
|
d7c1782bb6
|
全局美化 v4
|
2026-10-19 12:07:21 +08:00 |
|
tamakyi
|
5c7707ec0d
|
全局美化 v3
|
2026-10-19 11:43:24 +08:00 |
|
tamakyi
|
b165995a1c
|
全局美化 v2
|
2026-10-19 11:22:01 +08:00 |
|
tamakyi
|
d47329d505
|
全局美化 v1
|
2026-10-19 11:08:30 +08:00 |
|
tamakyi
|
b9a8afb2ec
|
fix footer v2
|
2026-10-19 10:20:15 +08:00 |
|
tamakyi
|
6789193052
|
fix footer
|
2026-10-19 10:09:57 +08:00 |
|
tamakyi
|
899b6d7f32
|
fix v16
|
2026-10-19 03:01:17 +08:00 |
|
tamakyi
|
dba991aa9d
|
fix v15
|
2026-10-19 02:37:59 +08:00 |
|
tamakyi
|
12a37e726f
|
fix v14
|
2026-10-19 02:25:19 +08:00 |
|
tamakyi
|
bd613a6f5a
|
fix v12
|
2026-10-19 01:11:28 +08:00 |
|
tamakyi
|
84f8afe8b1
|
restrict v11
|
2026-10-19 00:37:42 +08:00 |
|
tamakyi
|
8a0e58734c
|
restrict v10
|
2026-10-19 00:30:46 +08:00 |
|
tamakyi
|
efd5fce9a7
|
restrict v10
|
2026-10-19 00:07:22 +08:00 |
|
tamakyi
|
7e0e1cba49
|
restrict v9
|
2026-10-18 23:48:57 +08:00 |
|
tamakyi
|
7a6417c33d
|
restrict v8
|
2026-10-18 23:32:38 +08:00 |
|
tamakyi
|
317c96ca17
|
restrict v7
|
2026-10-18 23:04:50 +08:00 |
|
tamakyi
|
bd81398a06
|
restrict v6
|
2026-10-18 22:18:21 +08:00 |
|
tamakyi
|
6bd90779f8
|
restrict v1
|
2026-10-18 21:14:59 +08:00 |
|
tamakyi
|
5ee2dc1540
|
fix login error
|
2026-09-15 13:10:57 +08:00 |
|
tamakyi
|
0e0c16f786
|
fix deactivate
|
2026-09-15 12:10:42 +08:00 |
|
tamakyi
|
e683e798aa
|
fix typo error
|
2026-09-10 19:27:21 +08:00 |
|
tamakyi
|
8e2325eda5
|
aliyun sensor server
|
2026-09-10 18:04:19 +08:00 |
|