字号阶梯收尾 + 主色补到 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 全过。
This commit is contained in:
@@ -466,3 +466,33 @@ CLI 端到端手动验证:`honeypot hits` / `--ip` / `--show-password` 输出
|
||||
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(变大)。头像 50% / 胶囊 999px 有意保留。
|
||||
下一轮若要彻底统一:`templates/user/profile.html` 16 处 + `base.css` 自身 11 处;`mail/*` 不要动(邮件客户端不保证 var())。
|
||||
|
||||
## 18:3x 收尾:字号全令牌化 + 主色补到 AA(新增 --tb-on-primary)
|
||||
|
||||
**字号**:`templates/user/profile.html` 20 处、`base.css` 9 处全部换成 `--tb-fs-*`。
|
||||
base.css 里那 4 档导航字号(0.95/0.88/0.82/0.78)合并成「桌面 md → ≤1000 xs」,logo 三档统一 lg。
|
||||
**保留未改**(有理由):`.tb-pin-flag` / `.md-body code` 的 `0.9em`(相对父级,换成 rem 会破坏继承)、
|
||||
验证码的 `1.4rem/1.2rem`(等宽字形尺寸,不是正文阶梯)、chat 关闭按钮 `40px`、`password.html` 的 `3em`、mail/*(邮件客户端不保证 var())。
|
||||
行内 `12px` / `0.75rem` 这几处恰好等值,顺手换了;chat 里还剩一个 `13px`(13→12 会缩,没动)。
|
||||
|
||||
**主色**(同色相压亮度,目标 4.7 而非 4.5,留余量):base/screw `#1e87f0`(3.26~3.42) → **`#0e6fd0`**(4.70),
|
||||
shiroko `#4a7fc1`(3.69) → **`#3b6dac`**(4.74),sakura `#e5678f`(2.80) → **`#ba3e66`**(4.70,饱和度压到 .5 保持柔和粉)。
|
||||
`--tb-primary-weak` 与 shiroko 的两条 box-shadow 一并换 RGB;`TestSwatch` 的断言改成 `#ba3e66`。
|
||||
|
||||
⚠️ **主色有两个互相矛盾的角色**:当链接色要「深」,当按钮底色配白字却要「更深的底」——
|
||||
但深色模式的主色是亮强调(`#5c8dff`/`#ff8ab0`/`#6fa3e0`),白字只有 **2.2~3.1:1**。
|
||||
所以新增 **`--tb-on-primary`**(亮 `#ffffff` / 暗 `#0b1016`),`.uk-button-primary` 与
|
||||
`.uk-label:not(danger|warning|success|secondary)` 都吃它 → 深色模式按钮是「浅底深字」。实测 6.10/8.65/7.27。
|
||||
|
||||
其它泄漏点:header 验证码 focus 的 `#1e87f0`/`rgba(30,135,240)` → 令牌;profile `.feat-day.is-on` 写死的
|
||||
`rgba(30,135,240,.14)` → `var(--tb-primary-weak)`;explore 随机投票的 JS outline、vote-results 的行内色 → 令牌。
|
||||
|
||||
**深色 muted 之前算漏了**:只对 `--tb-bg` 算(4.7~4.8 以为过了),对**压平后的卡片**才是真实背景
|
||||
(sakura 4.31 / shiroko 4.17)。已提亮到 `#a18790` / `#7f8ea1`(4.62),base 深色 4.77 本来就过。
|
||||
|
||||
**生成器**:`paletteVars` 增加 `--tb-on-primary`(白字/深字里挑更优);`?dump=palette` 增加
|
||||
`onPrimaryCr` / `onPrimaryPickedBest` / `warnings` —— 主色卡在中间调时**不断言失败**(那是主色要调,
|
||||
不是挑错),列进 warnings;配色面板给橙色提示「压深配白字 / 提亮配深字」。
|
||||
`_scaffold.css` 令牌数 29 → 30。`StaticVersion` → **`20261021c`**。
|
||||
最终审计 8 种组合(base 亮暗 + 三主题亮暗)全部 ≥4.5。⚠️ 写 CSS 用 python 改时注意:`git checkout` 会把
|
||||
base.css 还原成 CRLF,插入行要用同一行尾,否则变 MIXED。
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
- 参考背景取「页面底色 + 压平后的卡片」两者更不利的那个;
|
||||
- 手填正文本身不达标时**原样返回**(宁可不够淡,不能产出看不见的字)。
|
||||
- 配色面板有读数(正文/次要/淡文字/主色),<4.5 变橙;默认 primary 已压到 `#3d6fac`(4.63)。
|
||||
- 还要导出 **`--tb-on-primary`**(白字 `#ffffff` / 深字 `#0b1016` 里挑对比度更高的):
|
||||
深色模式主色偏亮,白字只有 2.2~3.1:1。主色卡中间调时两种字都不够 → 面板橙色提示 +
|
||||
`?dump=palette` 的 `warnings`,**不判失败**(是主色要调,不是工具挑错)。
|
||||
- 自检钩子:`?dump=css|zip|validate|install|fx|fxpipe|fxkey|layers|fit|quality|palette`,`?demo=1&fresh=1&rows=N&scope=&loadurl=`
|
||||
(`?demo=1` 已含 fit 层 + 竖版背景,`@media` 才会生成)。用法:
|
||||
`chrome --headless=old --virtual-time-budget=12000 --dump-dom "http://127.0.0.1:8777/dev/theme-builder/index.html?demo=1&fresh=1&dump=fit"`
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
基座修了但主题页纹丝不动(sakura/shiroko 的 muted 就曾是 2.8:1,是这次才补到 4.6)。
|
||||
- 顶栏链接:亮色用 `--tb-text-secondary`、hover/当前页 `--tb-primary`;深色块原本 `!important` 定成
|
||||
`--tb-text`,会让同一顶栏在深浅两模式权重跳一档 → 已加 dark secondary 覆盖。
|
||||
- **主色要达到 AA 4.5:1**(它同时是 14px 链接/按钮文字色)。现值:base `#0e6fd0`、shiroko `#3b6dac`、
|
||||
sakura `#ba3e66`(都是同色相压亮度)。**改主色必须三处一起**:`base.css :root` + `themes/default.css` + 该主题。
|
||||
- ⚠️ **`--tb-on-primary`**(亮 `#ffffff` / 暗 `#0b1016`)是「主色作背景时的文字色」。
|
||||
深色模式的强调色偏亮(`#5c8dff`),白字只有 2.2~3.1:1 —— 用一个令牌同时伺候「链接色要深」和
|
||||
「按钮底要深」是矛盾的,所以拆成两个。`.uk-button-primary`、`.uk-label:not(语义色)` 都吃它。
|
||||
- 字号阶梯已全部落地(模板 + base.css);`0.9em`(相对父级)、验证码等宽字形、`mail/*` 是**有意例外**。
|
||||
- 视觉自检:`TEST_PREVIEW=1 go test ./templates -run TestGenPreview` → `outputs/preview-*.html`;
|
||||
截图必须 `chrome --headless=old --force-device-scale-factor=1`(新版 headless 本机挂住)。
|
||||
⚠️ `--window-size` ≠ CSS 视口;要精确视口就用 iframe 指定 width/height。验证 CSS 靠**像素比对**
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
- ⚠️ `ruleBody` 取令牌前要剥 `/* */`:注释会和后面的令牌粘成一个键,色块预览静默失效。
|
||||
- ⚠️ **主题会覆盖 base.css 的同名令牌 —— 基座改配色必须同步改主题**。改 `--tb-text-muted` 时
|
||||
sakura `#a08c94`(2.8:1)、shiroko `#8794a7`(2.76:1) 都还没跟上,"修了但主题页没变"就是这么来的。
|
||||
现已调到 `#78696e`(4.61) / `#656e7a`(4.63),同色相只压深。主色仍不达标(见 topics/style.md)。
|
||||
现已调到 `#78696e`(4.61) / `#656e7a`(4.63),深色块 `#a18790` / `#7f8ea1`(对压平后的卡片 4.62)。
|
||||
主色也已修(`#0e6fd0` / `#3b6dac` / `#ba3e66`)+ 新增 `--tb-on-primary`,见 topics/style.md。
|
||||
量对比度时半透明 `--tb-surface` 要**压平到 --tb-bg** 再算,别当纯白。
|
||||
- 主题选择页的色块由浅色块的 `--tb-bg` + `--tb-primary` 生成(`swatchOf`),注释里别写令牌名。
|
||||
- `test01` 是用户 2026-10-21 自建又删掉的验证主题(css + 2 张 webp 已移除,未提交)。
|
||||
|
||||
@@ -145,7 +145,13 @@ http://127.0.0.1:8777/dev/theme-builder/
|
||||
- 卡片是半透明(`rgba(255,255,255,0.86)`)时先**压平到页面底色**再算,否则白卡会被当成纯白、对比度偏低;
|
||||
- 同时检查页面底色与卡片两个背景,取更不利的那个;
|
||||
- 手填的正文本身就达不到目标时**原样返回**(不再往淡里调)——宁可不够淡,不能产出看不见的字;
|
||||
- 面板底部有读数 `正文 12.5:1 · 次要 5.5:1 · 淡文字 4.6:1 · 主色 3.4:1`,低于 4.5 变橙(手填色只提醒不拦)。
|
||||
- 面板底部有读数 `正文 12.5:1 · 次要 5.5:1 · 淡文字 4.6:1 · 主色 4.6:1`,低于 4.5 变橙(手填色只提醒不拦)。
|
||||
|
||||
主色除了当链接色,还当按钮/标签的**背景**,所以还要交代「压在上面的字」:
|
||||
导出时写 `--tb-on-primary`,在白字和深字(`#0b1016`)里挑对比度更高的那个。
|
||||
浅色主题主色深 → 白字;深色主题主色普遍偏亮(`#5c8dff` 之流)→ 白字只有 2.2~3.1:1,必须换深字。
|
||||
主色卡在**中间调**时两种字都不够 —— 面板会给橙色提示「压深一点配白字,或提亮一点配深字」,
|
||||
`?dump=palette` 里也会列进 `warnings`(不判失败:那是主色要调,不是工具挑错)。
|
||||
|
||||
顺带:这套字号阶梯(`--tb-fs-xs…hero`)来自 `base.css`,生成器**不碰字号** ——
|
||||
主题的职责是配色与背景,改字号会连带挤坏所有页面排版。
|
||||
|
||||
@@ -685,6 +685,13 @@ function textBackdrops(p){
|
||||
return [p.bg, flatten(p.surface, p.bg)];
|
||||
}
|
||||
|
||||
/** 主色作背景时的文字色:白字和深字里挑对比度更高的那个。
|
||||
浅色模式主色深 → 白字;深色模式主色普遍偏亮(#5c8dff 之流),白字只有 2~3:1,
|
||||
必须换成深字。不写这个令牌就只能吃 base.css 的默认值,深浅两套必然错一套。 */
|
||||
function onPrimary(primary){
|
||||
return contrastRatio('#ffffff', primary) >= contrastRatio('#0b1016', primary) ? '#ffffff' : '#0b1016';
|
||||
}
|
||||
|
||||
function paletteVars(p){
|
||||
const back = textBackdrops(p);
|
||||
return [
|
||||
@@ -699,6 +706,7 @@ function paletteVars(p){
|
||||
` --tb-text-muted: ${mixToContrast(p.text, back, 4.5)};`,
|
||||
` --tb-primary: ${p.primary};`,
|
||||
` --tb-primary-weak: ${withAlpha(p.primary, 0.12)};`,
|
||||
` --tb-on-primary: ${onPrimary(p.primary)};`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
@@ -1610,6 +1618,19 @@ function renderPalette(){
|
||||
).join(' · ') + '<br>低于 4.5:1(WCAG AA 正文)会变橙。次要/淡文字由正文自动派生并保底。';
|
||||
wrap.appendChild(crL);
|
||||
|
||||
// 主色卡在中间调时,白字和深字都不到 4.5 —— 这不是文字色挑错,是主色本身要动。
|
||||
const bestOn = Math.max(contrastRatio('#ffffff', S.palette.primary),
|
||||
contrastRatio('#0b1016', S.palette.primary));
|
||||
if (bestOn < 4.5){
|
||||
const warn = document.createElement('p');
|
||||
warn.className = 'hint';
|
||||
warn.style.color = '#e0a33e';
|
||||
warn.textContent = `⚠ 主色偏中间调:白字 ${contrastRatio('#ffffff', S.palette.primary).toFixed(2)}:1、`
|
||||
+ `深字 ${contrastRatio('#0b1016', S.palette.primary).toFixed(2)}:1,按钮上的字都不够清楚。`
|
||||
+ '把主色压深一点(配白字)或提亮一点(配深字)就能过。';
|
||||
wrap.appendChild(warn);
|
||||
}
|
||||
|
||||
const note = document.createElement('p');
|
||||
note.className = 'hint';
|
||||
note.textContent = '深色配色写在生成结果里(深色不加载图片);要改深色请直接改 CSS。';
|
||||
@@ -3038,7 +3059,7 @@ function boot(){
|
||||
const r = { cases: {} };
|
||||
try {
|
||||
const cases = {
|
||||
'默认亮色': { bg:'#eef3f9', surface:'rgba(255,255,255,0.86)', navbar:'rgba(255,255,255,0.52)', text:'#222d3b', primary:'#4a7fc1', border:'#d6e0ec' },
|
||||
'默认亮色': { bg:'#eef3f9', surface:'rgba(255,255,255,0.86)', navbar:'rgba(255,255,255,0.52)', text:'#222d3b', primary:'#3d6fac', border:'#d6e0ec' },
|
||||
'默认深色': { bg:'#141a22', surface:'#1d2530', navbar:'#141a22', text:'#e3eaf4', primary:'#6fa3e0', border:'#334052' },
|
||||
// 极端:文字色本身就不达标 → 必须原样返回,不能继续往底色淡
|
||||
'低对比手填': { bg:'#f4f4f4', surface:'#ffffff', navbar:'#ffffff', text:'#c8ccd2', primary:'#dddddd', border:'#e5e5e5' },
|
||||
@@ -3046,6 +3067,7 @@ function boot(){
|
||||
'白卡彩底': { bg:'#f6d9e4', surface:'#ffffff', navbar:'#f6d9e4', text:'#3b2b33', primary:'#c2557d', border:'#efd3de' },
|
||||
};
|
||||
let allOk = true;
|
||||
const warns = [];
|
||||
for (const name in cases){
|
||||
const p = cases[name];
|
||||
const back = textBackdrops(p);
|
||||
@@ -3057,9 +3079,21 @@ function boot(){
|
||||
muted: +worst(mut).toFixed(2),
|
||||
rawText: +worst(p.text).toFixed(2),
|
||||
mutedDarkerThanSecondary: worst(mut) <= worst(sec), // 层级:淡文字必须比次要更淡
|
||||
// 主色作底的文字:白字 vs 深字里必须挑更优的那个。
|
||||
// 注意这里不断言「一定 ≥4.5」:主色卡在中间调时,两种文字色都不够,
|
||||
// 这不是挑错了,而是主色本身要调 —— 列进 warnings 提醒,不判失败。
|
||||
onPrimary: onPrimary(p.primary),
|
||||
onPrimaryCr: +contrastRatio(onPrimary(p.primary), p.primary).toFixed(2),
|
||||
onPrimaryPickedBest: Math.abs(
|
||||
contrastRatio(onPrimary(p.primary), p.primary) -
|
||||
Math.max(contrastRatio('#ffffff', p.primary), contrastRatio('#0b1016', p.primary))
|
||||
) < 0.01,
|
||||
// 关键断言:只要有 4.5 的空间就必须达标;本来就不达标的不算错
|
||||
ok: (worst(p.text) < 4.5) ? (mut === p.text) : (worst(mut) >= 4.5 && worst(sec) >= 4.5),
|
||||
ok: ((worst(p.text) < 4.5) ? (mut === p.text) : (worst(mut) >= 4.5 && worst(sec) >= 4.5))
|
||||
&& Math.abs(contrastRatio(onPrimary(p.primary), p.primary) -
|
||||
Math.max(contrastRatio('#ffffff', p.primary), contrastRatio('#0b1016', p.primary))) < 0.01,
|
||||
};
|
||||
if (row.onPrimaryCr < 4.5) warns.push(name + '(主色 ' + p.primary + ' 中间调,白字/深字都不到 4.5)');
|
||||
// 卡片半透明时必须按压平后的颜色算,否则白卡 86% 会被当成 #fff
|
||||
row.surfaceFlat = flatten(p.surface, p.bg);
|
||||
r.cases[name] = row;
|
||||
@@ -3069,6 +3103,14 @@ function boot(){
|
||||
for (const name in r.cases){
|
||||
if (!r.cases[name].mutedDarkerThanSecondary) allOk = false;
|
||||
}
|
||||
// demo 的两套配色也要过:导出后的 CSS 直接吃这两个值
|
||||
r.demoLight = { primary: S.palette.primary, on: onPrimary(S.palette.primary),
|
||||
cr: +contrastRatio(onPrimary(S.palette.primary), S.palette.primary).toFixed(2) };
|
||||
r.demoDark = { primary: S.dark.primary, on: onPrimary(S.dark.primary),
|
||||
cr: +contrastRatio(onPrimary(S.dark.primary), S.dark.primary).toFixed(2) };
|
||||
if (r.demoLight.cr < 4.5) warns.push('demo 亮色(可改 palette.primary)');
|
||||
if (r.demoDark.cr < 4.5) warns.push('demo 深色(可改 dark.primary)');
|
||||
r.warnings = warns;
|
||||
// 把生成结果里的派生值也打出来,方便和 base.css 的取值对照
|
||||
const css = buildCSS(false);
|
||||
r.mutedLine = (css.match(/--tb-text-muted:[^;]+;/) || [''])[0];
|
||||
|
||||
@@ -10,7 +10,7 @@ var (
|
||||
BuildCommit = "dev"
|
||||
|
||||
// StaticVersion 静态资源缓存版本号:base.css 与主题 css 共用,改样式只改这一处。
|
||||
StaticVersion = "20261021b"
|
||||
StaticVersion = "20261021c"
|
||||
)
|
||||
|
||||
// 文本审核通道取值([security] text_censor_source)。定义在 conf 侧,
|
||||
|
||||
@@ -96,7 +96,7 @@ func TestSwatch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
sw := swatchOf(string(raw), "sakura")
|
||||
assert.True(t, strings.HasPrefix(sw, "linear-gradient("), "swatch 应为渐变串,实际 %q", sw)
|
||||
assert.Contains(t, sw, "#e5678f")
|
||||
assert.Contains(t, sw, "#ba3e66")
|
||||
|
||||
assert.Equal(t, "", swatchOf("/* 空文件 */", "sakura"), "取不到颜色时返回空串")
|
||||
assert.Equal(t, "", swatchOf(`html[data-theme-style="x"] { --tb-bg: url(evil); --tb-primary: #fff; }`, "x"),
|
||||
|
||||
+23
-14
@@ -20,8 +20,14 @@
|
||||
旧的 #999 只有 2.85:1,都低于 WCAG AA 正文要求的 4.5:1;
|
||||
这个值在 #fff 上 4.83:1、在 --tb-surface-2 上 4.6:1,是能兼顾「淡」和「看得清」的下限。 */
|
||||
--tb-text-muted: #6b7280;
|
||||
--tb-primary: #1e87f0;
|
||||
--tb-primary-weak: rgba(30, 135, 240, 0.10);
|
||||
/* 主色同时也是 14px 链接/按钮文字的颜色,必须满足 AA 4.5:1。
|
||||
#1e87f0 white 底上只有 3.42:1(大号字够 3:1,正文不够),压深到 #0e6fd0 = 4.70:1,
|
||||
白字压在按钮上反而更清楚(5.00:1)。色相不变,只降亮度。 */
|
||||
--tb-primary: #0e6fd0;
|
||||
--tb-primary-weak: rgba(14, 111, 208, 0.10);
|
||||
/* 主色作背景时的文字色。浅色模式主色深 → 白字;深色模式主色浅 → 深字。
|
||||
深色块里主色是「亮强调」(#5c8dff 之流),白字压上去只有 2.2~3.1:1。 */
|
||||
--tb-on-primary: #ffffff;
|
||||
--tb-radius: 12px;
|
||||
--tb-radius-sm: 8px;
|
||||
--tb-radius-xs: 6px;
|
||||
@@ -64,6 +70,7 @@
|
||||
--tb-text-muted: #8b93a1;
|
||||
--tb-primary: #5c8dff;
|
||||
--tb-primary-weak: rgba(92, 141, 255, 0.14);
|
||||
--tb-on-primary: #0b1016;
|
||||
--tb-shadow: 0 1px 2px rgba(0, 0, 0, 0.30), 0 12px 28px -18px rgba(0, 0, 0, 0.55);
|
||||
--tb-shadow-hover: 0 1px 2px rgba(0, 0, 0, 0.35), 0 18px 40px -20px rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
@@ -117,14 +124,14 @@
|
||||
/* 移动端优化:缩小字体和间距,避免右侧空白 */
|
||||
@media (max-width: 640px) {
|
||||
.uk-logo {
|
||||
font-size: 1rem;
|
||||
font-size: var(--tb-fs-lg);
|
||||
}
|
||||
.uk-navbar-nav a {
|
||||
font-size: 0.78rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
padding: 0 4px;
|
||||
}
|
||||
.emoji-placeholder {
|
||||
font-size: 1rem;
|
||||
font-size: var(--tb-fs-lg);
|
||||
}
|
||||
/* 移动端用户名限宽省略,避免长英文用户名撑爆顶栏 */
|
||||
.tb-nav-username {
|
||||
@@ -429,9 +436,9 @@
|
||||
color: var(--tb-primary);
|
||||
}
|
||||
|
||||
/* UIkit 组件跟随主题色:UIkit 3.3.3 的主色是编译期常量(#1e87f0),
|
||||
CSS 变量注入不进去,只能按组件覆盖。基础主题的 --tb-primary 恰好就是 #1e87f0,
|
||||
所以这段对默认外观零改动,只是让箱主主题能贯穿到按钮/链接/勾选。 */
|
||||
/* UIkit 组件跟随主题色:UIkit 3.3.3 的主色是编译期常量(写死在 uikit.min.css 里),
|
||||
CSS 变量注入不进去,只能按组件逐个覆盖成 var(--tb-primary)。
|
||||
少了这段,换主题后会出现「页面变粉、按钮还蓝」。 */
|
||||
a, .uk-link {
|
||||
color: var(--tb-primary);
|
||||
}
|
||||
@@ -440,6 +447,7 @@ a:hover, .uk-link:hover {
|
||||
}
|
||||
.uk-button-primary {
|
||||
background-color: var(--tb-primary);
|
||||
color: var(--tb-on-primary);
|
||||
}
|
||||
.uk-button-primary:hover,
|
||||
.uk-button-primary:focus {
|
||||
@@ -457,6 +465,7 @@ a:hover, .uk-link:hover {
|
||||
同特异性下 base.css 在 UIkit 之后加载,不排除会把它们一并刷成主色。 */
|
||||
.uk-label:not(.uk-label-danger):not(.uk-label-warning):not(.uk-label-success):not(.uk-label-secondary) {
|
||||
background-color: var(--tb-primary);
|
||||
color: var(--tb-on-primary);
|
||||
}
|
||||
.uk-input:focus,
|
||||
.uk-select:focus,
|
||||
@@ -554,7 +563,7 @@ a.tb-chip:hover {
|
||||
|
||||
/* 顶栏自适应:导航全部直接显示,窄屏时分级缩小字号/间距,保证英文/日文等长文字不溢出 */
|
||||
.uk-navbar-nav a {
|
||||
font-size: 0.95rem;
|
||||
font-size: var(--tb-fs-md);
|
||||
padding: 0 10px;
|
||||
}
|
||||
/* 未登录移动端占位:桌面端隐藏,仅 ≤640px 显示(见下方 media 查询)。
|
||||
@@ -566,17 +575,17 @@ a.tb-chip:hover {
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.uk-navbar-nav a {
|
||||
font-size: 0.88rem;
|
||||
font-size: var(--tb-fs-md);
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
.uk-navbar-nav a {
|
||||
font-size: 0.82rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
padding: 0 6px;
|
||||
}
|
||||
.uk-logo {
|
||||
font-size: 1.1rem;
|
||||
font-size: var(--tb-fs-lg);
|
||||
}
|
||||
/* 登录用户名限宽,过长省略号截断 */
|
||||
.tb-nav-username {
|
||||
@@ -590,11 +599,11 @@ a.tb-chip:hover {
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
.uk-navbar-nav a {
|
||||
font-size: 0.78rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
padding: 0 4px;
|
||||
}
|
||||
.uk-logo {
|
||||
font-size: 1rem;
|
||||
font-size: var(--tb-fs-lg);
|
||||
}
|
||||
.tb-nav-username {
|
||||
max-width: 80px;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
display / flex / grid、position 一律不动 —— 主题不是换皮模板。
|
||||
3. 本文件不参与构建,仅作参考;真正生效的是同目录下的 <slug>.css。
|
||||
|
||||
── 可用的 29 个令牌 ──────────────────────────────────────────────
|
||||
── 可用的 30 个令牌 ──────────────────────────────────────────────
|
||||
底色层 --tb-bg 页面底色(html/body)
|
||||
--tb-surface 卡片、输入框等前景面板
|
||||
--tb-surface-2 次级面(标签底、进度条轨道、代码块)
|
||||
@@ -31,6 +31,9 @@
|
||||
往浅调很容易掉到 3:1 以下(旧的 #8a919c 是 3.18:1),肉眼"淡一点"其实已经看不清了。
|
||||
强调 --tb-primary 主色:链接、主按钮、进度条
|
||||
--tb-primary-weak 主色 10% 左右的浅底(选中态、标签)
|
||||
--tb-on-primary 主色作背景时的文字色(默认白字;深色块一般是深字)
|
||||
主色同时是 14px 链接色,要 ≥4.5:1;但主色越深,
|
||||
白字压在按钮上越清楚 —— 所以深色模式反而不该用白字。
|
||||
形状 --tb-radius 卡片 12px / --tb-radius-sm 8px / --tb-radius-xs 6px
|
||||
阴影 --tb-shadow 卡片默认 / --tb-shadow-hover 悬停
|
||||
字体 --tb-font 字体栈(一般不改)
|
||||
@@ -83,8 +86,9 @@ html[data-theme-style="SLUG"] {
|
||||
--tb-text-secondary: #5c6370;
|
||||
--tb-text-muted: #6b7280; /* AA 下限:白底 4.83:1。别再往浅调 */
|
||||
|
||||
--tb-primary: #1e87f0;
|
||||
--tb-primary-weak: rgba(30, 135, 240, 0.10);
|
||||
--tb-primary: #0e6fd0; /* AA:白底 4.70:1。再浅就只够大号字了 */
|
||||
--tb-primary-weak: rgba(14, 111, 208, 0.10);
|
||||
--tb-on-primary: #ffffff;
|
||||
|
||||
--tb-radius: 12px;
|
||||
--tb-radius-sm: 8px;
|
||||
@@ -129,6 +133,7 @@ html[data-theme="dark"][data-theme-style="SLUG"] {
|
||||
|
||||
--tb-primary: #5c8dff;
|
||||
--tb-primary-weak: rgba(92, 141, 255, 0.14);
|
||||
--tb-on-primary: #0b1016; /* 深色主色偏亮,白字只有 3.13:1 */
|
||||
|
||||
--tb-shadow: 0 1px 2px rgba(0, 0, 0, 0.30), 0 12px 28px -18px rgba(0, 0, 0, 0.55);
|
||||
--tb-shadow-hover: 0 1px 2px rgba(0, 0, 0, 0.35), 0 18px 40px -20px rgba(0, 0, 0, 0.65);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
省掉一次静态请求;下方令牌仅用于设置页色块预览。 */
|
||||
html[data-theme-style="default"] {
|
||||
--tb-bg: #f7f8fa;
|
||||
--tb-primary: #1e87f0;
|
||||
--tb-primary: #0e6fd0;
|
||||
}
|
||||
|
||||
html[data-theme="dark"][data-theme-style="default"] {
|
||||
|
||||
@@ -17,8 +17,9 @@ html[data-theme-style="sakura"] {
|
||||
/* 弱化文字:旧值 #a08c94 只有 2.8:1(看不清),#78696e 是 4.6:1,踩住 AA 下限 */
|
||||
--tb-text-muted: #78696e;
|
||||
|
||||
--tb-primary: #e5678f;
|
||||
--tb-primary-weak: rgba(229, 103, 143, 0.12);
|
||||
/* 原来的 #e5678f 在粉底上只有 2.80:1(链接几乎看不清);同色相压深到 #ba3e66 = 4.70:1 */
|
||||
--tb-primary: #ba3e66;
|
||||
--tb-primary-weak: rgba(186, 62, 102, 0.12);
|
||||
|
||||
--tb-shadow: 0 1px 2px rgba(120, 60, 80, 0.05), 0 12px 28px -18px rgba(120, 60, 80, 0.22);
|
||||
--tb-shadow-hover: 0 1px 2px rgba(120, 60, 80, 0.07), 0 18px 40px -20px rgba(120, 60, 80, 0.30);
|
||||
@@ -38,7 +39,7 @@ html[data-theme="dark"][data-theme-style="sakura"] {
|
||||
|
||||
--tb-text: #f2e4e9;
|
||||
--tb-text-secondary: #c6aab4;
|
||||
--tb-text-muted: #9d818b;
|
||||
--tb-text-muted: #a18790;
|
||||
|
||||
--tb-primary: #ff8ab0;
|
||||
--tb-primary-weak: rgba(255, 138, 176, 0.16);
|
||||
|
||||
@@ -24,8 +24,9 @@ html[data-theme-style="shiroko"] {
|
||||
/* 弱化文字:旧值 #8794a7 只有 2.76:1(看不清),#656e7a 是 4.63:1,踩住 AA 下限 */
|
||||
--tb-text-muted: #656e7a;
|
||||
|
||||
--tb-primary: #4a7fc1;
|
||||
--tb-primary-weak: rgba(74, 127, 193, 0.11);
|
||||
/* 原来的 #4a7fc1 在 #eef3f9 上只有 3.69:1;同色相压深到 #3b6dac = 4.74:1 */
|
||||
--tb-primary: #3b6dac;
|
||||
--tb-primary-weak: rgba(59, 109, 172, 0.11);
|
||||
|
||||
--tb-shadow: 0 1px 2px rgba(38, 56, 82, 0.05), 0 18px 44px -22px rgba(38, 56, 82, 0.30);
|
||||
--tb-shadow-hover: 0 1px 2px rgba(38, 56, 82, 0.07), 0 24px 56px -24px rgba(38, 56, 82, 0.38);
|
||||
@@ -69,7 +70,7 @@ html[data-theme="dark"][data-theme-style="shiroko"] {
|
||||
|
||||
--tb-text: #e3eaf4;
|
||||
--tb-text-secondary: #a6b4c8;
|
||||
--tb-text-muted: #77869b;
|
||||
--tb-text-muted: #7f8ea1;
|
||||
|
||||
--tb-primary: #6fa3e0;
|
||||
--tb-primary-weak: rgba(111, 163, 224, 0.16);
|
||||
@@ -105,10 +106,10 @@ html[data-theme="dark"][data-theme-style="shiroko"] {
|
||||
html[data-theme-style="shiroko"] .uk-card-default {
|
||||
backdrop-filter: blur(16px) saturate(1.05);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(1.05);
|
||||
box-shadow: 0 0 0 1px rgba(74, 127, 193, 0.12), var(--tb-shadow);
|
||||
box-shadow: 0 0 0 1px rgba(59, 109, 172, 0.12), var(--tb-shadow);
|
||||
}
|
||||
html[data-theme-style="shiroko"] .uk-card-default.uk-card-hover:hover {
|
||||
box-shadow: 0 0 0 1px rgba(74, 127, 193, 0.18), var(--tb-shadow-hover);
|
||||
box-shadow: 0 0 0 1px rgba(59, 109, 172, 0.18), var(--tb-shadow-hover);
|
||||
}
|
||||
html[data-theme-style="shiroko"] .uk-navbar-container:not(.uk-navbar-transparent) {
|
||||
background-color: var(--tb-navbar);
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.captcha-equation-box:focus-within {
|
||||
border-color: #1e87f0;
|
||||
box-shadow: 0 0 0 3px rgba(30,135,240,0.12);
|
||||
border-color: var(--tb-primary);
|
||||
box-shadow: 0 0 0 3px var(--tb-primary-weak);
|
||||
}
|
||||
.math-captcha-modal-question {
|
||||
font-size: 1.4rem;
|
||||
@@ -76,8 +76,8 @@
|
||||
background: #fff;
|
||||
}
|
||||
#captcha-modal-answer:focus {
|
||||
border-color: #1e87f0;
|
||||
box-shadow: 0 0 0 2px rgba(30,135,240,0.2);
|
||||
border-color: var(--tb-primary);
|
||||
box-shadow: 0 0 0 2px var(--tb-primary-weak);
|
||||
}
|
||||
/* 隐藏数字输入框的上下箭头 */
|
||||
#captcha-modal-answer::-webkit-inner-spin-button,
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<strong class="uk-margin-small-left" data-page-node-id="V7t5pBo43sNBv1a6rvp0jv">
|
||||
{{ if $isMyMessage }}{{t $.LangCode "chat.me"}}{{ else }}{{t $.LangCode "chat.other"}}{{ end }}
|
||||
</strong>
|
||||
<span class="uk-text-meta uk-margin-small-left" style="font-size: 0.75rem;" data-page-node-id="JidfstvpABpsDBPRL3WY6I">
|
||||
<span class="uk-text-meta uk-margin-small-left" style="font-size: var(--tb-fs-xs);" data-page-node-id="JidfstvpABpsDBPRL3WY6I">
|
||||
{{ .CreatedAt.Format "01-02 15:04" }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -221,7 +221,7 @@
|
||||
<div x-data="{ files: null }" class="tb-img-uploader">
|
||||
<div uk-form-custom>
|
||||
<a href="#" class="uk-icon-link" uk-icon="image" style="margin-left: 10px"></a>
|
||||
<span style="font-size: 12px" x-text="files ? files[0].name : '{{t .LangCode "ask.addImage"}}'"></span>
|
||||
<span style="font-size: var(--tb-fs-xs)" x-text="files ? files[0].name : '{{t .LangCode "ask.addImage"}}'"></span>
|
||||
<input id="uploader" name="images" type="file" accept="image/*" x-on:change="files = Object.values($event.target.files)">
|
||||
</div>
|
||||
<button x-show="files" type="button" class="uk-icon-link" uk-icon="close" style="margin-left: 10px;"
|
||||
@@ -253,7 +253,7 @@
|
||||
<div x-data="{ files: null }" class="tb-img-uploader">
|
||||
<div uk-form-custom>
|
||||
<a href="#" class="uk-icon-link" uk-icon="image" style="margin-left: 10px"></a>
|
||||
<span style="font-size: 12px" x-text="files ? files[0].name : '{{t .LangCode "ask.addImage"}}'"></span>
|
||||
<span style="font-size: var(--tb-fs-xs)" x-text="files ? files[0].name : '{{t .LangCode "ask.addImage"}}'"></span>
|
||||
<input id="uploader" name="images" type="file" accept="image/*" x-on:change="files = Object.values($event.target.files)">
|
||||
</div>
|
||||
<button x-show="files" type="button" class="uk-icon-link" uk-icon="close" style="margin-left: 10px;"
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
<div x-data="{ files: null }" class="tb-img-uploader">
|
||||
<div uk-form-custom>
|
||||
<a href="#" class="uk-icon-link" uk-icon="image" style="margin-left: 10px"></a>
|
||||
<span style="font-size: 12px" x-text="files ? files[0].name : '{{t .LangCode "ask.addImage"}}'"></span>
|
||||
<span style="font-size: var(--tb-fs-xs)" x-text="files ? files[0].name : '{{t .LangCode "ask.addImage"}}'"></span>
|
||||
<input id="uploader" name="images" type="file" accept="image/*" x-on:change="files = Object.values($event.target.files)">
|
||||
</div>
|
||||
<button x-show="files" type="button" class="uk-icon-link" uk-icon="close" style="margin-left: 10px;"
|
||||
|
||||
@@ -508,7 +508,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (!votes.length) return;
|
||||
const target = votes[Math.floor(Math.random() * votes.length)];
|
||||
randomVoteBtn.classList.add('is-spinning');
|
||||
target.style.outline = '2px solid #1e87f0';
|
||||
target.style.outline = '2px solid var(--tb-primary)';
|
||||
target.style.outlineOffset = '2px';
|
||||
window.setTimeout(function() {
|
||||
window.location.href = target.href;
|
||||
|
||||
+22
-22
@@ -46,7 +46,7 @@
|
||||
}
|
||||
.theme-btn {
|
||||
margin: 2px;
|
||||
font-size: 0.8rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
}
|
||||
.social-item {
|
||||
display: flex;
|
||||
@@ -88,7 +88,7 @@
|
||||
background: transparent;
|
||||
color: var(--tb-text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
font-size: var(--tb-fs-md);
|
||||
white-space: nowrap;
|
||||
transition: background 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
@@ -123,10 +123,10 @@
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.feat-card-head h3 { font-size: 1.05rem; }
|
||||
.feat-card-head h3 { font-size: var(--tb-fs-lg); }
|
||||
.feat-status {
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.72rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
line-height: 1.6;
|
||||
padding: 0 8px;
|
||||
border-radius: var(--tb-radius-xs);
|
||||
@@ -135,7 +135,7 @@
|
||||
.feat-status.is-closed { background: #f0506e; color: #fff; }
|
||||
.feat-hint {
|
||||
display: block;
|
||||
font-size: 0.78rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
line-height: 1.5;
|
||||
color: var(--tb-text-muted);
|
||||
}
|
||||
@@ -143,12 +143,12 @@
|
||||
.feat-field > label > span {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 0.875rem;
|
||||
font-size: var(--tb-fs-md);
|
||||
}
|
||||
.feat-field-label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 0.875rem;
|
||||
font-size: var(--tb-fs-md);
|
||||
}
|
||||
/* 收集模式三选一(长期开放 / 定时收集 / 循环定时),胶囊样式复用 .feat-day */
|
||||
.feat-modes {
|
||||
@@ -174,7 +174,7 @@
|
||||
padding: 3px 8px;
|
||||
border: 1px solid var(--tb-border);
|
||||
border-radius: 999px;
|
||||
font-size: 0.78rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
color: var(--tb-text-secondary);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
@@ -194,7 +194,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.82rem;
|
||||
font-size: var(--tb-fs-md);
|
||||
color: var(--tb-text-secondary);
|
||||
}
|
||||
.feat-schedule-times input[type="time"] {
|
||||
@@ -209,7 +209,7 @@
|
||||
}
|
||||
html[data-theme="dark"] .feat-day.is-on {
|
||||
border-color: var(--tb-primary);
|
||||
background: rgba(30, 135, 240, 0.14);
|
||||
background: var(--tb-primary-weak);
|
||||
color: var(--tb-primary);
|
||||
}
|
||||
html[data-theme="dark"] .feat-schedule-times input[type="time"] {
|
||||
@@ -218,7 +218,7 @@
|
||||
color: var(--tb-text);
|
||||
}
|
||||
.feat-quota-line {
|
||||
font-size: 0.82rem;
|
||||
font-size: var(--tb-fs-md);
|
||||
color: var(--tb-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
@@ -248,7 +248,7 @@
|
||||
}
|
||||
.chat-zone hr { margin: 14px 0; }
|
||||
.chat-zone .uk-legend {
|
||||
font-size: 1.05rem;
|
||||
font-size: var(--tb-fs-lg);
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
@@ -272,8 +272,8 @@
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--tb-radius-xs);
|
||||
background: var(--tb-primary);
|
||||
color: #fff;
|
||||
font-size: 0.875rem;
|
||||
color: var(--tb-on-primary);
|
||||
font-size: var(--tb-fs-md);
|
||||
line-height: 1.8;
|
||||
}
|
||||
.tb-tag-chip button {
|
||||
@@ -281,7 +281,7 @@
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-size: 1rem;
|
||||
font-size: var(--tb-fs-lg);
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
opacity: 0.75;
|
||||
@@ -294,14 +294,14 @@
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-size: 0.875rem;
|
||||
font-size: var(--tb-fs-md);
|
||||
}
|
||||
.tb-tag-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
color: var(--tb-text-muted);
|
||||
}
|
||||
.tb-tag-error { color: #f0506e; }
|
||||
@@ -323,14 +323,14 @@
|
||||
}
|
||||
.tb-pcard-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 1.02rem;
|
||||
font-size: var(--tb-fs-lg);
|
||||
font-weight: 600;
|
||||
}
|
||||
/* 全页纵向压缩:面板内表单字段间距由 UIkit 默认 20px 收紧到 12px */
|
||||
.profile-panel .uk-margin { margin-bottom: 12px; }
|
||||
.tb-pcard-title .tb-pcard-extra {
|
||||
float: right;
|
||||
font-size: 0.76rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
font-weight: 400;
|
||||
color: var(--tb-text-muted);
|
||||
line-height: 1.6;
|
||||
@@ -380,7 +380,7 @@
|
||||
.tb-ratio-tag {
|
||||
display: inline-block;
|
||||
margin: 8px 0 0;
|
||||
font-size: 0.72rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
line-height: 1;
|
||||
color: var(--tb-text-secondary);
|
||||
background: #f4f4f4;
|
||||
@@ -390,7 +390,7 @@
|
||||
}
|
||||
.tb-file-hint {
|
||||
display: block;
|
||||
font-size: 0.78rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
line-height: 1.5;
|
||||
color: var(--tb-text-muted);
|
||||
margin-top: 6px;
|
||||
@@ -419,7 +419,7 @@
|
||||
display: block;
|
||||
margin: 0 0 10px;
|
||||
text-align: center;
|
||||
font-size: 0.78rem;
|
||||
font-size: var(--tb-fs-xs);
|
||||
line-height: 1.6;
|
||||
color: var(--tb-text-muted);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
{{ $replies := index $.TextReplies $stat.Field.Label }}
|
||||
{{ if $replies }}
|
||||
<div class="uk-margin-small-top">
|
||||
<div class="uk-text-small" style="cursor:pointer; user-select:none; color:#1e87f0;" onclick="toggleFieldReplies('{{ $stat.Field.ID }}')" title="{{t $.LangCode "vote.resultsToggleHint"}}">
|
||||
<div class="uk-text-small" style="cursor:pointer; user-select:none; color:var(--tb-primary);" onclick="toggleFieldReplies('{{ $stat.Field.ID }}')" title="{{t $.LangCode "vote.resultsToggleHint"}}">
|
||||
<span id="caret-{{ $stat.Field.ID }}" style="display:inline-block; transition:transform .15s; transform:rotate(-90deg);">▾</span>
|
||||
{{t $.LangCode "vote.resultsViewTexts"}}({{ len $replies }})
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user