Files
tamabox/templates/question/new-question-template.html
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

233 lines
9.5 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{ if .AskClosedMessage }}
<div>
<div uk-alert class="uk-text-center">
<p>{{ .AskClosedMessage }}</p>
{{ if .AskNextOpenAtStr }}
<p class="uk-text-small uk-text-muted">{{t .LangCode "ask.nextOpenAt" .AskNextOpenAtStr}}</p>
{{ end }}
{{ if .AskLastCloseAtStr }}
<p class="uk-text-small uk-text-muted">{{t .LangCode "ask.lastCloseAt" .AskLastCloseAtStr}}</p>
{{ end }}
</div>
<!-- 提问功能关闭但私聊开启:保留独立私聊入口(提问与私聊是独立功能) -->
{{ if .AllowChat }}
<div class="uk-margin-top">
{{ if .LoggedUser }}
<form method="post" action="/_/{{.PageUser.Domain}}" id="chat-only-form" enctype="multipart/form-data">
{{ .CSRFTokenHTML }}
{{ if .PageUser.AskPassword }}
<input type="hidden" name="ask_password" id="ask_password_input" value="">
{{ end }}
<input type="hidden" name="start_chat_val" value="true">
<input type="hidden" name="g-recaptcha-response" id="chat-g-recaptcha-response" value="">
<div class="uk-margin uk-text-center">
<textarea name="content" class="uk-textarea" rows="4" placeholder="{{t .LangCode "ask.chatPlaceholder"}}" maxlength="{{ .Limits.ChatMessageLen }}"></textarea>
</div>
<div class="uk-margin uk-text-center">
{{ if eq .CaptchaType "math" }}
<button type="button" class="uk-button uk-button-secondary math-captcha-trigger" id="chat-only-submit-btn">{{t .LangCode "ask.chat"}}</button>
{{ else }}
<button type="submit" class="uk-button uk-button-secondary g-recaptcha"
data-sitekey="{{.RecaptchaSiteKey}}" data-callback="onChatOnlySubmit"
id="chat-only-submit-btn">{{t .LangCode "ask.chat"}}</button>
{{ end }}
<div class="uk-text-meta">{{t .LangCode "ask.chatLimit" .PageUser.ChatMaxMessages}}</div>
</div>
</form>
{{ else }}
<!-- 未登录:私聊需登录 -->
<div class="uk-text-center uk-margin">
<p class="uk-text-small uk-text-muted">{{t .LangCode "ask.loginRequiredChat"}}</p>
<a class="uk-button uk-button-secondary uk-button-small" href="/login?to={{.CurrentURI}}">{{t .LangCode "ask.goLogin"}}</a>
</div>
{{ end }}
</div>
{{ end }}
{{ if and .AllowChat .LoggedUser }}
{{ if ne .CaptchaType "math" }}
<!-- 独立私聊表单的验证码回调(提问表单的 onSubmit 只提交 #form) -->
<script>
function onChatOnlySubmit(token) {
var form = document.getElementById('chat-only-form');
if (!form) return;
var btn = document.getElementById('chat-only-submit-btn');
if (btn) { btn.disabled = true; btn.innerText = '{{t .LangCode "common.verifying"}}'; }
var needPassword = {{ if .PageUser.AskPassword }}true{{ else }}false{{ end }};
if (needPassword) {
UIkit.modal.prompt('{{t .LangCode "ask.needPassword"}}', '', 'password')
.then(function(password) {
if (password) {
var pw = form.querySelector('#ask_password_input');
if (pw) pw.value = password;
form.submit();
} else {
if (btn) { btn.disabled = false; btn.innerText = '{{t .LangCode "ask.chat"}}'; }
}
});
} else {
form.submit();
}
}
</script>
{{ end }}
{{ end }}
</div>
{{ else if not .CanAsk }}
<div>
<div uk-alert class="uk-text-center">
<p>{{t .LangCode "ask.needLogin"}}</p>
</div>
<div class="uk-margin uk-text-center">
<a class="uk-button uk-button-primary" href="/login?to={{.CurrentURI}}">{{t .LangCode "ask.goLogin"}}</a>
</div>
</div>
{{ else }}
<form method="post" action="/_/{{.PageUser.Domain}}" enctype="multipart/form-data" id="form">
{{ .CSRFTokenHTML }}
{{if .PageUser.AskPassword}}
<input type="hidden" name="ask_password" id="ask_password_input" value="">
{{end}}
<input type="hidden" name="start_chat_val" id="start_chat_val" value="false">
<input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response" value="">
<div class="uk-margin uk-text-center">
<textarea name="content" class="uk-textarea" rows="5" placeholder="{{t .LangCode "ask.placeholder"}}"
maxlength="{{ .Limits.QuestionContentLen }}">{{.content}}</textarea>
<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: 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;"
@click="files = null"></button>
</div>
</div>
<div class="uk-margin uk-grid-small">
<label class="uk-text-small">
<input name="private" class="uk-checkbox" type="checkbox"> {{t .LangCode "ask.private"}}
</label>
<label class="uk-text-small">
<input name="receive_reply_via_email" id="notify-checkbox" class="uk-checkbox" type="checkbox"> {{t .LangCode "ask.notify"}}
</label>
<div id="email-container" style="width: 100%; display: none;" class="uk-margin-small-top">
<input class="uk-input uk-form-small" type="email" name="receive_reply_email" id="email-input" placeholder="{{t .LangCode "ask.emailPlaceholder"}}">
</div>
</div>
<div class="uk-margin uk-text-center">
<div class="uk-grid-small uk-flex-center" uk-grid>
<div>
{{ if eq .CaptchaType "math" }}
<button type="button" class="uk-button uk-button-primary math-captcha-trigger" id="normal-submit-btn">{{t .LangCode "ask.submit"}}</button>
{{ else }}
<button type="submit" class="uk-button uk-button-primary g-recaptcha"
data-sitekey="{{.RecaptchaSiteKey}}" data-callback="onSubmit"
id="normal-submit-btn">{{t .LangCode "ask.submit"}}</button>
{{ end }}
</div>
{{ if not .AllowChat }}
<div>
<button type="button" class="uk-button uk-button-secondary" disabled
uk-tooltip="{{t .LangCode "ask.chatDisabled"}}">{{t .LangCode "ask.chat"}}</button>
</div>
{{ else if .LoggedUser }}
<div>
<button type="button" class="uk-button uk-button-secondary" id="chat-submit-btn">{{t .LangCode "ask.chat"}}</button>
<div class="uk-text-meta">{{t .LangCode "ask.chatLimit" .PageUser.ChatMaxMessages}}</div>
</div>
{{ else }}
<div>
<button type="button" class="uk-button uk-button-secondary" id="chat-submit-btn">{{t .LangCode "ask.chat"}}</button>
</div>
{{ end }}
</div>
</div>
</form>
<script>
// 邮箱显隐与验证逻辑
var notifyCheckbox = document.getElementById('notify-checkbox');
var emailContainer = document.getElementById('email-container');
var emailInput = document.getElementById('email-input');
if (notifyCheckbox && emailContainer && emailInput) {
notifyCheckbox.addEventListener('change', function() {
if (this.checked) {
emailContainer.style.display = 'block';
emailInput.setAttribute('required', 'required');
} else {
emailContainer.style.display = 'none';
emailInput.removeAttribute('required');
emailInput.value = '';
}
});
}
// 私聊按钮:设置标识后触发验证弹窗
var chatBtn = document.getElementById('chat-submit-btn');
if (chatBtn) {
chatBtn.addEventListener('click', function() {
var isLogged = {{ if .LoggedUser }}true{{ else }}false{{ end }};
if (!isLogged) {
var currentURI = '{{.CurrentURI}}';
UIkit.modal.dialog(
'<div class="uk-text-center uk-padding" style="min-height: 150px; display: flex; flex-direction: column; justify-content: center;">' +
'<h3 class="uk-modal-title uk-margin-small-bottom">{{t .LangCode "ask.loginRequired"}}</h3>' +
'<p class="uk-text-muted uk-margin-small-bottom">{{t .LangCode "ask.loginRequiredChat"}}</p>' +
'<a href="/login?to=' + encodeURIComponent(currentURI) + '" class="uk-button uk-button-primary">{{t .LangCode "ask.goLogin"}}</a>' +
'</div>',
{width: '400px'}
);
return;
}
document.getElementById('start_chat_val').value = 'true';
{{ if eq .CaptchaType "math" }}
var submitBtn = document.getElementById('normal-submit-btn');
if (submitBtn) submitBtn.click();
{{ else }}
document.getElementById('normal-submit-btn').click();
{{ end }}
});
}
{{ if ne .CaptchaType "math" }}
function onSubmit(token) {
var btn = document.querySelector('.g-recaptcha');
var originalText = btn.innerText;
if (notifyCheckbox && notifyCheckbox.checked && emailInput && emailInput.value.trim() === '') {
alert(CAPTCHA_I18N.needEmail);
grecaptcha.reset();
return;
}
btn.disabled = true;
btn.innerText = '{{t .LangCode "common.verifying"}}';
var needPassword = {{if .PageUser.AskPassword}}true{{else}}false{{end}};
if (needPassword) {
UIkit.modal.prompt(CAPTCHA_I18N.needPassword, '', 'password')
.then(function(password) {
if (password) {
document.getElementById('ask_password_input').value = password;
document.getElementById('form').submit();
} else {
btn.disabled = false;
btn.innerText = originalText;
grecaptcha.reset();
}
});
} else {
document.getElementById('form').submit();
}
}
{{ end }}
</script>
{{ end }}