This commit is contained in:
tamakyi
2026-10-19 16:16:07 +08:00
parent e9fba8b9ed
commit 3202ef9821
54 changed files with 12000 additions and 75 deletions
+21
View File
@@ -385,3 +385,24 @@ P1 剩余两项(首屏跟随系统深色、首页 Waline 懒加载)用户明
3. `writePreview` 用全文搜 `data-theme="dark"` 判断是否已加属性 → 命中内联主题 css 里的选择器,深色预览没生效;
改为只看 `<html ...>` 开标签。
4. 脚本改 form/user.go 时把插入块写在结构体中间,拼出 `} Language ...`;靠 Read 复查发现并重写。
## 主题 follow-up(部署后反馈两处)
1. **/user/* 不生效主题**:根因是 `ctx.Data["ThemeSlug"]` 只在 `question.Pager`(`/_/{domain}`)里注入,
`/user/*` 完全没走这条链路。修法:`internal/context/context.go` 的 Contexter 里按
`isThemeScope(path) && c.IsLogged` 注入 `theme.Resolve(c.User.Theme)`;
`isThemeScope` 只认 `/user` 与 `/user/`(不能用裸 `HasPrefix("/user")`,会误吞 `/username`)。
新增 `internal/context/context_test.go` 守住路径白名单;page.go 的注释同步改。
公开页(`/`、`/explore`、`/vote/{token}`)**仍不跟随**登录用户主题,保持站点默认。
2. **sakura 显示奇怪**(用户选择:顶栏白与粉底割裂 / 卡片纯白太突 / 底色分布不均):
- base.css 新增令牌 `--tb-navbar`(`:root` 里 `var(--tb-surface)`,亮暗两条 navbar 规则都改成
`var(--tb-navbar, var(--tb-surface))`),主题可单独给顶栏上色。
- sakura v2:底色改**纯色** `#fceef3`(不用渐变——渐变会被读成"不均匀"),
顶栏 `#fff6f9`、卡片 `#fffafc`(暖白,不再是纯白硬切),网点从 16px/0.14 放宽到 24px/0.05。
- 静态版本 `20261019f`;`_scaffold.css` 令牌说明 19→20。
- `profile.theme.hint` 四语言文案改成「提问箱页面 + 本人后台生效,公开页保持默认」。
- `zz_preview_test.go` 抽出 `inlineThemeCSS()`,并新增 `preview-profile-sakura-*`(后台页面也要在主题下检查)。
验证:`go build ./...`(须带 webp overlay)、i18n/theme/context/templates 四个包测试全绿。
渲染法:抓线上 `/_/admin6` HTML → 把 base.css 指到本地新文件、主题 css 内联 → 无头 Chrome `--headless=old` 截图。
+14
View File
@@ -153,3 +153,17 @@
`reports --domain|--email [--days]`(证据包)、`--pending`、`--stats --days N`(Top 被举报箱主 + Top 举报 IP);
`hide-public [--only-reported] [--since N]`。
- 本地冒烟:`NEKOBOX_CONFIG_PATH=<ini>`;`conf.Init` 强制 `app.external_url`、`server.salt`、`server.xsrf_key` 非空。
## 主题(箱主个性化)
- 两个注入点,别再漏:`/_/{domain}/*` → `question.Pager` 按 `pageUser.Theme`;
`/user/*` → `internal/context` 的 Contexter 按 `c.User.Theme`(`isThemeScope()` 只认 `/user` 与 `/user/`)。
公开页(`/`、`/explore`、`/vote/{token}`)不跟随登录用户主题。
- 主题 = 一个 css 文件只覆盖令牌;新增主题不用改 Go/模板,放 `static/css/themes/<slug>.css` +
`[theme] available` 登记即可(`static/fs.go` 已嵌整个目录)。底稿 `_scaffold.css`。
- **必须同时写浅色块与深色块**(`html[data-theme-style=x]` / `html[data-theme="dark"][data-theme-style=x]`),
`TestThemeCSSComplete` 会查;色块预览由浅色块的 `--tb-bg` + `--tb-primary` 自动生成(色值过正则防注入)。
- 令牌 `--tb-navbar`:顶栏底色,默认 `var(--tb-surface)`。页面底色偏彩时主题应覆盖它,
否则顶栏是纯白带、压在彩底上割裂(用户实测反馈过)。
- 主题底色用**纯色**,别用整页渐变——会被读成「底色分布不均匀」;图案建议 ≥24px、透明度 ≤0.06。
- 改了 base.css / 主题 css 必须 +1 `conf.StaticVersion`(两者共用同一个 v)。
+1 -1
View File
@@ -10,7 +10,7 @@ var (
BuildCommit = "dev"
// StaticVersion 静态资源缓存版本号:base.css 与主题 css 共用,改样式只改这一处。
StaticVersion = "20261019e"
StaticVersion = "20261019f"
)
// 文本审核通道取值([security] text_censor_source)。定义在 conf 侧,
+15
View File
@@ -26,6 +26,7 @@ import (
"github.com/tamakyi/TamaBox/internal/i18n"
"github.com/tamakyi/TamaBox/internal/shared"
templatepkg "github.com/tamakyi/TamaBox/internal/template"
"github.com/tamakyi/TamaBox/internal/theme"
)
type EndpointType string
@@ -239,6 +240,13 @@ func Contexter() flamego.Handler {
isUserPage := strings.HasPrefix(path, "/user") || strings.HasPrefix(path, "/_/")
c.Data["IsUserPage"] = isUserPage
// 主题:/user 后台是箱主本人的页面,跟随本人配置;
// /_/{domain} 的箱主主题由 question.Pager 覆盖(访客看到的是箱主的选择)。
// 空串(未选且站点无默认主题)= 不加载主题 css。
if isThemeScope(path) && c.IsLogged {
c.Data["ThemeSlug"] = theme.Resolve(c.User.Theme)
}
// SEO:仅公开页(首页/注册/登录/explore)允许被搜索引擎收录;
// 提问内容(提问箱页、单题页)、密码页、私信、用户后台等隐私页一律 noindex。
isPublicPage := path == "/" || path == "/register" || path == "/login" || strings.HasPrefix(path, "/explore")
@@ -327,3 +335,10 @@ func (c *Context) Lang() string {
}
return i18n.Default
}
// isThemeScope 判断该路径的主题是否由「当前登录用户自己的配置」决定。
// 只覆盖 /user 后台(本人页面);提问箱相关页面由 question.Pager 按箱主注入,
// 公开页(首页/探索/登录)保持站点默认外观,不跟随登录状态变化。
func isThemeScope(path string) bool {
return path == "/user" || strings.HasPrefix(path, "/user/")
}
+30
View File
@@ -0,0 +1,30 @@
package context
import "testing"
// 主题作用域:/user 后台必须命中(否则设置页、题目管理页拿不到自己的主题),
// 其余路径不得命中(提问箱页面由 question.Pager 按箱主注入,公开页保持站点默认)。
func TestIsThemeScope(t *testing.T) {
cases := map[string]bool{
"/user": true,
"/user/": true,
"/user/profile": true,
"/user/questions": true,
"/user/votes/1/results": true,
"/username": false, // 前缀相近但不是用户后台
"/_/admin6": false,
"/_/admin6/share-card": false,
"/": false,
"/explore": false,
"/vote/abc": false,
"/api/v1/user/admin6": false,
"/api/v1/user": false,
"/deactivated": false,
}
for path, want := range cases {
if got := isThemeScope(path); got != want {
t.Errorf("isThemeScope(%q) = %v, want %v", path, got, want)
}
}
}
+1 -1
View File
@@ -933,7 +933,7 @@ var enDict = map[string]string{
// 主题:设置页选择器与主题显示名(theme.name.<slug> 由 internal/theme 约定)
"profile.theme.title": "Theme",
"profile.theme.hint": "Applies to your question box pages only; the rest of the site keeps the default look.",
"profile.theme.hint": "Applies to your question box pages and your dashboard; public pages (home, explore) keep the default look.",
"profile.theme.followDefault": "Follow site default",
"profile.theme.save": "Save theme",
"profile.theme.saved": "Theme saved",
+1 -1
View File
@@ -933,7 +933,7 @@ var jaDict = map[string]string{
// 主题:设置页选择器与主题显示名(theme.name.<slug> 由 internal/theme 约定)
"profile.theme.title": "テーマ",
"profile.theme.hint": "質問箱のページにのみ適用されます。他のページは既定の外観のままです。",
"profile.theme.hint": "質問箱のページと管理画面に適用されます。ホーム・探索などの公開ページは既定の外観のままです。",
"profile.theme.followDefault": "サイト既定に従う",
"profile.theme.save": "テーマを保存",
"profile.theme.saved": "テーマを保存しました",
+1 -1
View File
@@ -874,7 +874,7 @@ var zhCNDict = map[string]string{
// 主题:设置页选择器与主题显示名(theme.name.<slug> 由 internal/theme 约定)
"profile.theme.title": "主题外观",
"profile.theme.hint": "仅对提问箱页面生效,站内其它页面保持默认外观。",
"profile.theme.hint": "对提问箱页面和你的后台页面生效,首页/探索等公开页保持默认外观。",
"profile.theme.followDefault": "跟随站点默认",
"profile.theme.save": "保存主题",
"profile.theme.saved": "主题已保存",
+1 -1
View File
@@ -933,7 +933,7 @@ var zhTWDict = map[string]string{
// 主题:设置页选择器与主题显示名(theme.name.<slug> 由 internal/theme 约定)
"profile.theme.title": "主題外觀",
"profile.theme.hint": "僅對提問箱頁面生效,站內其他頁面維持預設外觀。",
"profile.theme.hint": "對提問箱頁面與你的後台頁面生效,首頁/探索等公開頁維持預設外觀。",
"profile.theme.followDefault": "跟隨站點預設",
"profile.theme.save": "儲存主題",
"profile.theme.saved": "主題已儲存",
Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+109 -5
View File
@@ -11,6 +11,8 @@
--tb-surface: #ffffff;
--tb-surface-2: #f4f6f8;
--tb-surface-hover: #f3f7ff;
/* 顶栏底色:默认跟随卡片面;主题想消除「纯白顶栏压在彩色底上」的割裂时单独覆盖 */
--tb-navbar: var(--tb-surface);
--tb-border: #e8eaee;
--tb-text: #1f2329;
--tb-text-secondary: #5c6370;
@@ -23,6 +25,10 @@
--tb-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 12px 28px -18px rgba(16, 24, 40, 0.20);
--tb-shadow-hover: 0 1px 2px rgba(16, 24, 40, 0.05), 0 18px 40px -20px rgba(16, 24, 40, 0.28);
--tb-font: system-ui, -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Microsoft YaHei", sans-serif;
/* 整页背景:主题可覆盖。图案层叠在背景图之上,两者都不用时保持纯色底。 */
--tb-page-image: none;
--tb-page-pattern: none;
--tb-page-pattern-size: auto;
}
html[data-theme="dark"] {
--tb-bg: #1a1d23;
@@ -134,7 +140,7 @@
color: var(--tb-text) !important;
}
html[data-theme="dark"] .uk-navbar-container {
background-color: var(--tb-surface) !important;
background-color: var(--tb-navbar, var(--tb-surface)) !important;
color: var(--tb-text) !important;
}
html[data-theme="dark"] .uk-navbar-nav > li > a,
@@ -239,12 +245,19 @@
html[data-theme="dark"] .explore-intro {
color: var(--tb-text-muted) !important;
}
/* 切换按钮图标切换 */
/* 切换按钮图标切换(内联 SVG,不依赖 UIkit 图标库) */
#theme-toggle .tb-icon-light, #theme-toggle-mobile .tb-icon-light,
#theme-toggle .tb-icon-dark, #theme-toggle-mobile .tb-icon-dark {
display: inline-flex;
align-items: center;
}
#theme-toggle .tb-icon-light svg, #theme-toggle-mobile .tb-icon-light svg,
#theme-toggle .tb-icon-dark svg, #theme-toggle-mobile .tb-icon-dark svg { display: block; }
#theme-toggle .tb-icon-dark, #theme-toggle-mobile .tb-icon-dark { display: none; }
html[data-theme="dark"] #theme-toggle .tb-icon-light,
html[data-theme="dark"] #theme-toggle-mobile .tb-icon-light { display: none; }
html[data-theme="dark"] #theme-toggle .tb-icon-dark,
html[data-theme="dark"] #theme-toggle-mobile .tb-icon-dark { display: inline; }
html[data-theme="dark"] #theme-toggle-mobile .tb-icon-dark { display: inline-flex; }
/* 基础质感层:只调整外观(圆角/投影/字体/数字对齐),不改任何结构与布局。
颜色一律取自头部令牌,亮暗两套自动生效,因此这里不需要再写 html[data-theme="dark"] 分支。 */
@@ -253,6 +266,12 @@
body {
font-family: var(--tb-font);
background-color: var(--tb-bg);
/* 主题的整页背景:图案(平铺)在上,图片(cover)在下,都不设置时等价于纯色 */
background-image: var(--tb-page-pattern, none), var(--tb-page-image, none);
background-size: var(--tb-page-pattern-size, auto), cover;
background-repeat: repeat, no-repeat;
background-position: left top, center center;
background-attachment: scroll, fixed;
color: var(--tb-text);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -306,7 +325,7 @@
box-shadow: var(--tb-shadow);
}
.uk-navbar-container:not(.uk-navbar-transparent) {
background-color: var(--tb-surface);
background-color: var(--tb-navbar, var(--tb-surface));
}
/* 键盘可达性:仅在键盘聚焦时出现描边,鼠标点击不出现 */
.uk-button:focus-visible,
@@ -320,6 +339,89 @@
color: var(--tb-text-muted);
}
/* 主题选择器(设置页):色块 + 名称。纯展示,不参与主题令牌,所以放在基础层。 */
.tb-theme-hint {
margin: 0 0 6px;
font-size: 0.8rem;
color: var(--tb-text-muted);
}
.tb-theme-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 8px;
margin: 8px 0 2px;
}
.tb-theme-opt {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 9px;
border: 1px solid var(--tb-border);
border-radius: var(--tb-radius-sm);
cursor: pointer;
}
.tb-theme-opt:hover {
background-color: var(--tb-surface-hover);
}
.tb-theme-opt input {
margin: 0;
}
.tb-theme-swatch {
flex: 0 0 auto;
width: 26px;
height: 26px;
border-radius: var(--tb-radius-xs);
border: 1px solid var(--tb-border);
background-color: var(--tb-surface-2);
}
/* 选中态用描边而不是 :has(),兼容老 WebView */
.tb-theme-opt input:checked + .tb-theme-swatch {
outline: 2px solid var(--tb-primary);
outline-offset: 1px;
}
.tb-theme-name {
font-size: 0.84rem;
line-height: 1.3;
}
.tb-theme-opt input:checked ~ .tb-theme-name {
color: var(--tb-primary);
}
/* UIkit 组件跟随主题色:UIkit 3.3.3 的主色是编译期常量(#1e87f0),
CSS 变量注入不进去,只能按组件覆盖。基础主题的 --tb-primary 恰好就是 #1e87f0,
所以这段对默认外观零改动,只是让箱主主题能贯穿到按钮/链接/勾选。 */
a, .uk-link {
color: var(--tb-primary);
}
a:hover, .uk-link:hover {
color: var(--tb-primary);
}
.uk-button-primary {
background-color: var(--tb-primary);
}
.uk-button-primary:hover,
.uk-button-primary:focus {
background-color: var(--tb-primary);
filter: brightness(1.07);
}
.uk-checkbox:checked,
.uk-checkbox:indeterminate,
.uk-radio:checked {
background-color: var(--tb-primary);
border-color: transparent;
}
.uk-input:focus,
.uk-select:focus,
.uk-textarea:focus {
border-color: var(--tb-primary);
}
.uk-tab > .uk-active > a {
border-color: var(--tb-primary);
}
.uk-text-primary {
color: var(--tb-primary) !important;
}
/* 轻量胶囊:用于投票类型、热门标签等「只是分类信息」的位置。
实心 uk-label 一屏多个会变成一排色块,这里统一降级为浅底 + 次要色文字。 */
.tb-chip {
@@ -609,11 +711,13 @@ a.tb-chip:hover {
border-radius: var(--tb-radius-xs);
}
.uk-progress::-webkit-progress-value {
/* 两端都圆:值 100% 时若只圆左边,右端方角会顶出轨道圆角 */
background-color: var(--tb-primary);
border-radius: var(--tb-radius-xs) 0 0 var(--tb-radius-xs);
border-radius: var(--tb-radius-xs);
}
.uk-progress::-moz-progress-bar {
background-color: var(--tb-primary);
border-radius: var(--tb-radius-xs);
}
/* 移动端底部操作栏:固定底栏承载语言切换与深色模式,桌面端隐藏 */
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · </title>
<title>预览 16:08 · </title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
@@ -636,7 +636,7 @@ document.getElementById("introcolor").style.color = "#ffffff";
href="/_/tester/1">查看回答</a>
<div class="uk-text-left uk-text-small uk-text-muted">
<span class="tb-pin-flag" title="已置顶">⭐</span>
2026-10-19 15:15:39
2026-10-19 16:08:02
</div>
<p class="uk-text-small uk-text-break tb-preline tb-clamp-5">置顶的问题
</p>
@@ -648,7 +648,7 @@ document.getElementById("introcolor").style.color = "#ffffff";
href="/_/tester/2">查看回答</a>
<div class="uk-text-left uk-text-small uk-text-muted">
2026-10-19 14:15:39
2026-10-19 15:08:02
</div>
<p class="uk-text-small uk-text-break tb-preline tb-clamp-5">普通问题
</p>
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · </title>
<title>预览 16:08 · </title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
@@ -636,7 +636,7 @@ document.getElementById("introcolor").style.color = "#ffffff";
href="/_/tester/1">查看回答</a>
<div class="uk-text-left uk-text-small uk-text-muted">
<span class="tb-pin-flag" title="已置顶">⭐</span>
2026-10-19 15:15:39
2026-10-19 16:08:02
</div>
<p class="uk-text-small uk-text-break tb-preline tb-clamp-5">置顶的问题
</p>
@@ -648,7 +648,7 @@ document.getElementById("introcolor").style.color = "#ffffff";
href="/_/tester/2">查看回答</a>
<div class="uk-text-left uk-text-small uk-text-muted">
2026-10-19 14:15:39
2026-10-19 15:08:02
</div>
<p class="uk-text-small uk-text-break tb-preline tb-clamp-5">普通问题
</p>
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · 投票</title>
<title>预览 16:08 · 投票</title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · 投票</title>
<title>预览 16:08 · 投票</title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · TamaBox</title>
<title>预览 16:08 · TamaBox</title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · TamaBox</title>
<title>预览 16:08 · TamaBox</title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · 设置</title>
<title>预览 16:08 · 设置</title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · 设置</title>
<title>预览 16:08 · 设置</title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · 设置</title>
<title>预览 16:08 · 设置</title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+19 -14
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · </title>
<title>预览 16:08 · </title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
@@ -22,13 +22,17 @@
<link rel="stylesheet" href="/static/css/base.css?v="/>
<style>/* 樱花(sakura):粉调 + 细网点图案,纯 CSS 实现,不依赖图片素材。
复制 _scaffold.css 写的示例:只覆盖令牌,未改动任何布局。 */
<style>/* 樱花(sakura):粉调 + 极淡网点,纯 CSS 实现,不依赖图片素材。
只覆盖令牌,不改动任何布局。要点:
- 页面底色用纯色(不用渐变),保证整页色感一致;
- 顶栏与卡片都是「带粉调的暖白」而不是纯白,和粉底之间是过渡不是硬切;
- 网点放大到 24px、透明度降到 0.05,只作纸纹,不在留白处抢注意力。 */
html[data-theme-style="sakura"] {
--tb-bg: #fff6f8;
--tb-surface: #ffffff;
--tb-surface-2: #fdeef2;
--tb-bg: #fceef3;
--tb-surface: #fffafc;
--tb-surface-2: #fbe8ee;
--tb-surface-hover: #fdf1f5;
--tb-navbar: #fff6f9;
--tb-border: #f2dde4;
--tb-text: #3a2b30;
@@ -42,16 +46,17 @@ html[data-theme-style="sakura"] {
--tb-shadow-hover: 0 1px 2px rgba(120, 60, 80, 0.07), 0 18px 40px -20px rgba(120, 60, 80, 0.30);
--tb-page-image: none;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(229, 103, 143, 0.14) 1px, transparent 0);
--tb-page-pattern-size: 16px 16px;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(229, 103, 143, 0.05) 1px, transparent 1.4px);
--tb-page-pattern-size: 24px 24px;
}
html[data-theme="dark"][data-theme-style="sakura"] {
--tb-bg: #241a1f;
--tb-surface: #2e2129;
--tb-surface: #2f2228;
--tb-surface-2: #3a2a33;
--tb-surface-hover: rgba(255, 138, 176, 0.10);
--tb-border: #49333d;
--tb-navbar: #2a1e24;
--tb-border: #4a333e;
--tb-text: #f2e4e9;
--tb-text-secondary: #c6aab4;
@@ -64,8 +69,8 @@ html[data-theme="dark"][data-theme-style="sakura"] {
--tb-shadow-hover: 0 1px 2px rgba(0, 0, 0, 0.40), 0 18px 40px -20px rgba(0, 0, 0, 0.68);
--tb-page-image: none;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(255, 138, 176, 0.10) 1px, transparent 0);
--tb-page-pattern-size: 16px 16px;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(255, 138, 176, 0.05) 1px, transparent 1.4px);
--tb-page-pattern-size: 24px 24px;
}
</style>
@@ -682,7 +687,7 @@ document.getElementById("introcolor").style.color = "#6b545c";
href="/_/tester/1">查看回答</a>
<div class="uk-text-left uk-text-small uk-text-muted">
<span class="tb-pin-flag" title="已置顶">⭐</span>
2026-10-19 15:15:39
2026-10-19 16:08:02
</div>
<p class="uk-text-small uk-text-break tb-preline tb-clamp-5">置顶的问题
</p>
@@ -694,7 +699,7 @@ document.getElementById("introcolor").style.color = "#6b545c";
href="/_/tester/2">查看回答</a>
<div class="uk-text-left uk-text-small uk-text-muted">
2026-10-19 14:15:39
2026-10-19 15:08:02
</div>
<p class="uk-text-small uk-text-break tb-preline tb-clamp-5">普通问题
</p>
+19 -14
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>预览 15:15 · </title>
<title>预览 16:08 · </title>
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<meta name="description" content="狼的提问箱——一个无需注册、可匿名提问的匿名提问箱,安全地倾诉与回应。">
<meta name="keywords" content="匿名提问箱,提问箱,匿名提问,狼的提问箱,匿名问答,匿名提问网站">
@@ -22,13 +22,17 @@
<link rel="stylesheet" href="/static/css/base.css?v="/>
<style>/* 樱花(sakura):粉调 + 细网点图案,纯 CSS 实现,不依赖图片素材。
复制 _scaffold.css 写的示例:只覆盖令牌,未改动任何布局。 */
<style>/* 樱花(sakura):粉调 + 极淡网点,纯 CSS 实现,不依赖图片素材。
只覆盖令牌,不改动任何布局。要点:
- 页面底色用纯色(不用渐变),保证整页色感一致;
- 顶栏与卡片都是「带粉调的暖白」而不是纯白,和粉底之间是过渡不是硬切;
- 网点放大到 24px、透明度降到 0.05,只作纸纹,不在留白处抢注意力。 */
html[data-theme-style="sakura"] {
--tb-bg: #fff6f8;
--tb-surface: #ffffff;
--tb-surface-2: #fdeef2;
--tb-bg: #fceef3;
--tb-surface: #fffafc;
--tb-surface-2: #fbe8ee;
--tb-surface-hover: #fdf1f5;
--tb-navbar: #fff6f9;
--tb-border: #f2dde4;
--tb-text: #3a2b30;
@@ -42,16 +46,17 @@ html[data-theme-style="sakura"] {
--tb-shadow-hover: 0 1px 2px rgba(120, 60, 80, 0.07), 0 18px 40px -20px rgba(120, 60, 80, 0.30);
--tb-page-image: none;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(229, 103, 143, 0.14) 1px, transparent 0);
--tb-page-pattern-size: 16px 16px;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(229, 103, 143, 0.05) 1px, transparent 1.4px);
--tb-page-pattern-size: 24px 24px;
}
html[data-theme="dark"][data-theme-style="sakura"] {
--tb-bg: #241a1f;
--tb-surface: #2e2129;
--tb-surface: #2f2228;
--tb-surface-2: #3a2a33;
--tb-surface-hover: rgba(255, 138, 176, 0.10);
--tb-border: #49333d;
--tb-navbar: #2a1e24;
--tb-border: #4a333e;
--tb-text: #f2e4e9;
--tb-text-secondary: #c6aab4;
@@ -64,8 +69,8 @@ html[data-theme="dark"][data-theme-style="sakura"] {
--tb-shadow-hover: 0 1px 2px rgba(0, 0, 0, 0.40), 0 18px 40px -20px rgba(0, 0, 0, 0.68);
--tb-page-image: none;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(255, 138, 176, 0.10) 1px, transparent 0);
--tb-page-pattern-size: 16px 16px;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(255, 138, 176, 0.05) 1px, transparent 1.4px);
--tb-page-pattern-size: 24px 24px;
}
</style>
@@ -682,7 +687,7 @@ document.getElementById("introcolor").style.color = "#6b545c";
href="/_/tester/1">查看回答</a>
<div class="uk-text-left uk-text-small uk-text-muted">
<span class="tb-pin-flag" title="已置顶">⭐</span>
2026-10-19 15:15:39
2026-10-19 16:08:02
</div>
<p class="uk-text-small uk-text-break tb-preline tb-clamp-5">置顶的问题
</p>
@@ -694,7 +699,7 @@ document.getElementById("introcolor").style.color = "#6b545c";
href="/_/tester/2">查看回答</a>
<div class="uk-text-left uk-text-small uk-text-muted">
2026-10-19 14:15:39
2026-10-19 15:08:02
</div>
<p class="uk-text-small uk-text-break tb-preline tb-clamp-5">普通问题
</p>
Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

+1 -1
View File
@@ -78,7 +78,7 @@ func Pager(ctx context.Context) {
}
// 主题只作用于箱主相关页面(提问箱主页 / 单题页 / 密码页 / 分享卡路由),
// 与背景图、颜色同属箱主个性化配置;/user 后台保持中性外观。
// 与背景图、颜色同属箱主个性化配置;/user 后台由 Contexter 按登录用户自己的配置注入。
// 空串 = 不加载主题 css(站点默认就是基础外观时省掉一次请求)。
ctx.Data["ThemeSlug"] = theme.Resolve(pageUser.Theme)
// 页面密码检查
+4 -2
View File
@@ -11,6 +11,8 @@
--tb-surface: #ffffff;
--tb-surface-2: #f4f6f8;
--tb-surface-hover: #f3f7ff;
/* 顶栏底色:默认跟随卡片面;主题想消除「纯白顶栏压在彩色底上」的割裂时单独覆盖 */
--tb-navbar: var(--tb-surface);
--tb-border: #e8eaee;
--tb-text: #1f2329;
--tb-text-secondary: #5c6370;
@@ -138,7 +140,7 @@
color: var(--tb-text) !important;
}
html[data-theme="dark"] .uk-navbar-container {
background-color: var(--tb-surface) !important;
background-color: var(--tb-navbar, var(--tb-surface)) !important;
color: var(--tb-text) !important;
}
html[data-theme="dark"] .uk-navbar-nav > li > a,
@@ -323,7 +325,7 @@
box-shadow: var(--tb-shadow);
}
.uk-navbar-container:not(.uk-navbar-transparent) {
background-color: var(--tb-surface);
background-color: var(--tb-navbar, var(--tb-surface));
}
/* 键盘可达性:仅在键盘聚焦时出现描边,鼠标点击不出现 */
.uk-button:focus-visible,
+4 -1
View File
@@ -15,11 +15,13 @@
display / flex / grid、position 一律不动 —— 主题不是换皮模板。
3. 本文件不参与构建,仅作参考;真正生效的是同目录下的 <slug>.css。
── 可用的 19 个令牌 ──────────────────────────────────────────────
── 可用的 20 个令牌 ──────────────────────────────────────────────
底色层 --tb-bg 页面底色(html/body)
--tb-surface 卡片、输入框等前景面板
--tb-surface-2 次级面(标签底、进度条轨道、代码块)
--tb-surface-hover 悬停底(列表行、下拉项)
--tb-navbar 顶栏底色(不写则同 --tb-surface;
页面底色不是白时建议覆盖成 bg 或它的同色,否则顶栏会成为一条白带)
线条 --tb-border 边框、分隔线
文字 --tb-text 正文
--tb-text-secondary 次要文字(说明、meta)
@@ -50,6 +52,7 @@ html[data-theme-style="SLUG"] {
--tb-surface: #ffffff;
--tb-surface-2: #f4f6f8;
--tb-surface-hover: #f3f7ff;
--tb-navbar: var(--tb-surface); /* 顶栏底色,需要时改成 var(--tb-bg) */
--tb-border: #e8eaee;
--tb-text: #1f2329;
+16 -11
View File
@@ -1,10 +1,14 @@
/* 樱花(sakura):粉调 + 细网点图案,纯 CSS 实现,不依赖图片素材。
复制 _scaffold.css 写的示例:只覆盖令牌,未改动任何布局。 */
/* 樱花(sakura):粉调 + 极淡网点,纯 CSS 实现,不依赖图片素材。
只覆盖令牌,不改动任何布局。要点:
- 页面底色用纯色(不用渐变),保证整页色感一致;
- 顶栏与卡片都是「带粉调的暖白」而不是纯白,和粉底之间是过渡不是硬切;
- 网点放大到 24px、透明度降到 0.05,只作纸纹,不在留白处抢注意力。 */
html[data-theme-style="sakura"] {
--tb-bg: #fff6f8;
--tb-surface: #ffffff;
--tb-surface-2: #fdeef2;
--tb-bg: #fceef3;
--tb-surface: #fffafc;
--tb-surface-2: #fbe8ee;
--tb-surface-hover: #fdf1f5;
--tb-navbar: #fff6f9;
--tb-border: #f2dde4;
--tb-text: #3a2b30;
@@ -18,16 +22,17 @@ html[data-theme-style="sakura"] {
--tb-shadow-hover: 0 1px 2px rgba(120, 60, 80, 0.07), 0 18px 40px -20px rgba(120, 60, 80, 0.30);
--tb-page-image: none;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(229, 103, 143, 0.14) 1px, transparent 0);
--tb-page-pattern-size: 16px 16px;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(229, 103, 143, 0.05) 1px, transparent 1.4px);
--tb-page-pattern-size: 24px 24px;
}
html[data-theme="dark"][data-theme-style="sakura"] {
--tb-bg: #241a1f;
--tb-surface: #2e2129;
--tb-surface: #2f2228;
--tb-surface-2: #3a2a33;
--tb-surface-hover: rgba(255, 138, 176, 0.10);
--tb-border: #49333d;
--tb-navbar: #2a1e24;
--tb-border: #4a333e;
--tb-text: #f2e4e9;
--tb-text-secondary: #c6aab4;
@@ -40,6 +45,6 @@ html[data-theme="dark"][data-theme-style="sakura"] {
--tb-shadow-hover: 0 1px 2px rgba(0, 0, 0, 0.40), 0 18px 40px -20px rgba(0, 0, 0, 0.68);
--tb-page-image: none;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(255, 138, 176, 0.10) 1px, transparent 0);
--tb-page-pattern-size: 16px 16px;
--tb-page-pattern: radial-gradient(circle at 1px 1px, rgba(255, 138, 176, 0.05) 1px, transparent 1.4px);
--tb-page-pattern-size: 24px 24px;
}
+1 -1
View File
@@ -7,7 +7,7 @@
{
"collectionId": "tamabox",
"collectionName": "tamabox-changelogs",
"content": "新增:箱主主题功能。设置页可为主题箱主页与单题页选择配色,只影响配色、图案与整页背景,不影响布局;未选择时跟随站点默认主题。<br>\n新增:主题底稿 static/css/themes/_scaffold.css,复制改名为 &lt;slug&gt;.css 并在 [theme] available 登记即可新增一套主题,无需改模板;示例主题「樱花」。",
"content": "新增:箱主主题功能。设置页可为主题选择配色、图案与整页背景(不影响布局);提问箱页面(箱主页 / 单题页 / 密码页 / 分享卡)与箱主本人后台页面都会跟随所选主题,首页、探索等公开页保持站点默认外观;未选择时跟随站点默认主题。<br>\n新增:主题底稿 static/css/themes/_scaffold.css,复制改名为 &lt;slug&gt;.css 并在 [theme] available 登记即可新增一套主题,无需改模板;示例主题「樱花」。",
"created": "2026-10-19 15:10:00.000Z",
"id": "none",
"published_at": "2026-10-19 15:10:00.000Z",
+35 -8
View File
@@ -80,19 +80,12 @@ func TestGenPreview(t *testing.T) {
themeBox["PageUser"].(map[string]interface{})["Background"] = bgURI
themeBox["PageUser"].(map[string]interface{})["Usernamecolor"] = "#3a2b30"
themeBox["PageUser"].(map[string]interface{})["Introcolor"] = "#6b545c"
themeCSS, err := os.ReadFile(filepath.Join("..", "static", "css", "themes", "sakura.css"))
if err != nil {
t.Fatalf("read theme css: %v", err)
}
for _, theme := range []string{"light", "dark"} {
page, err := renderToString(t, "question/list", themeBox)
if err != nil {
t.Fatalf("render themed list: %v", err)
}
// 预览页走 file://,把主题 css 内联进去(真实页面是一次带缓存的静态请求)。
// 用正则匹配:预览数据里没有 StaticVersion,v 参数为空串,不能按固定串替换。
link := regexp.MustCompile(`<link rel="stylesheet" href="/static/css/themes/sakura\.css\?v=[^"]*"/>`)
page = link.ReplaceAllString(page, "<style>"+string(themeCSS)+"</style>")
page = inlineThemeCSS(t, page, "sakura")
writePreview(t, outDir, "preview-theme-sakura-"+theme+".html", page, theme, "../static")
}
@@ -159,6 +152,40 @@ func TestGenPreview(t *testing.T) {
}
writePreview(t, outDir, "preview-profile-account-"+theme+".html", page, theme, "../static")
}
// 设置页在箱主主题下的样子:/user 后台现在也会跟随本人主题,
// 需要确认「粉色页面 + 主题选择器」不会互相吃掉。
profileSakura := profileBaseData()
profileSakura["Themes"] = profile["Themes"]
profileSakura["CurrentTheme"] = "sakura"
profileSakura["ActiveProfileTab"] = "account"
profileSakura["ThemeSlug"] = "sakura"
profileSakura["FeatureQuestion"] = nil
profileSakura["FeatureChat"] = nil
profileSakura["FeatureVote"] = nil
for _, theme := range []string{"light", "dark"} {
page, err := renderToString(t, "user/profile", profileSakura)
if err != nil {
t.Fatalf("render themed profile: %v", err)
}
page = inlineThemeCSS(t, page, "sakura")
writePreview(t, outDir, "preview-profile-sakura-"+theme+".html", page, theme, "../static")
}
}
// inlineThemeCSS 把主题 <link> 换成内联 style:预览页走 file://,相对路径取不到。
// 预览数据里没有 StaticVersion,v 参数为空串,所以按正则匹配而不是固定串替换。
func inlineThemeCSS(t *testing.T, html, slug string) string {
t.Helper()
css, err := os.ReadFile(filepath.Join("..", "static", "css", "themes", slug+".css"))
if err != nil {
t.Fatalf("read theme css: %v", err)
}
link := regexp.MustCompile(`<link rel="stylesheet" href="/static/css/themes/` + slug + `\.css\?v=[^"]*"/>`)
if !link.MatchString(html) {
t.Fatalf("主题 %s 的 <link> 未出现在页面里(ThemeSlug 没传到模板)", slug)
}
return link.ReplaceAllString(html, "<style>"+string(css)+"</style>")
}
// writePreview 把 CDN 换成可直连的镜像、钉死主题,仅用于本地预览。