Files
tamabox/.workbuddy/backup/tamami-load-inject-20260906/header.html
T
tamakyi e3e59e7c69 chore: baseline snapshot before moderation-strictness feature
Repo .git was damaged (refs/ missing, index truncated, object store near-empty).
Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak,
then full worktree committed as a rollback baseline.
2026-09-10 10:56:27 +08:00

1020 lines
41 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="{{.LangCode}}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>{{ .Title }}</title>
<meta name="description" content="{{ if .MetaDescription }}{{ .MetaDescription }}{{ else }}{{ t .LangCode "site.description" }}{{ end }}">
<meta name="keywords" content="{{ t .LangCode "site.keywords" }}">
<!-- 预连接:UIkit 走公共 CDN(cdnjs.snrat.com),其余自托管库仍在本站 R2,均需预连接 -->
<link rel="preconnect" href="https://r2.337845818.best" crossorigin>
<link rel="preconnect" href="https://cdnjs.snrat.com" crossorigin>
<!-- UIkit 3.3.3 切到公共 CDN(cdnjs.snrat.com,国内实测全量下载 ~0.25s,原 R2 自建 ~1.4-2.0s) -->
<link rel="stylesheet" href="https://cdnjs.snrat.com/ajax/libs/uikit/3.3.3/css/uikit.min.css"/>
<!-- Waline 评论区样式:非阻塞加载,不阻塞首屏渲染 -->
<link rel="stylesheet" href="https://r2.337845818.best/tamabox/cdn/waline/client/v2/waline-no-commits.css" media="print" onload="this.media='all'"/>
<script defer src="https://cdnjs.snrat.com/ajax/libs/uikit/3.3.3/js/uikit.min.js"></script>
<script defer src="https://cdnjs.snrat.com/ajax/libs/uikit/3.3.3/js/uikit-icons.min.js"></script>
<!-- UIkit 补丁:复刻原自建版 "modify-black-font"(官方版与自建版仅 3 处颜色差异——把有色容器内默认白字/白标题改为黑字)。
模板当前只用到 .uk-light 容器(用户背景封面,question/list.html、question-not-found.html;
其中 h3/p 已由 #usernamecolor/#introcolor 内联 style 覆盖),故只需补该容器的 3 处投影即可保持渲染一致。
若日后新增 .uk-card-primary/.uk-card-secondary/.uk-section-primary/.uk-tile-*/.uk-overlay-primary 等容器且需黑字,须同步扩展。 -->
<style>
.uk-light { color: rgba(0,0,0,.7); }
.uk-light .uk-h1, .uk-light .uk-h2, .uk-light .uk-h3, .uk-light .uk-h4, .uk-light .uk-h5, .uk-light .uk-h6,
.uk-light .uk-heading-2xlarge, .uk-light .uk-heading-large, .uk-light .uk-heading-medium, .uk-light .uk-heading-small, .uk-light .uk-heading-xlarge,
.uk-light h1, .uk-light h2, .uk-light h3, .uk-light h4, .uk-light h5, .uk-light h6 { color: #000; }
.uk-light .uk-table th { color: rgba(0,0,0,.7); }
</style>
<!-- dayjs/alpinejs/vconsole/waline.js 已迁 cdnjs.snrat.com(sha256 与官方一致,国内实测快 2-7x) -->
<script defer src="https://cdnjs.snrat.com/ajax/libs/dayjs/1.10.8/dayjs.min.js"></script>
<script defer src="https://cdnjs.snrat.com/ajax/libs/alpinejs/3.9.0/cdn.min.js"></script>
{{ if .VConsole }}
<script src="https://cdnjs.snrat.com/ajax/libs/vConsole/3.12.1/vconsole.min.js" type="application/javascript"></script>
<script>new window.VConsole()</script>
{{ end }}
<!-- jQuery 已改为按需加载:仅 easy.qrcode 二维码页(list/question-not-found/profile)自行引入 -->
<script defer src="https://r2.337845818.best/tamabox/cdn/time.box.js"></script>
<script defer src="https://cdnjs.snrat.com/ajax/libs/waline/2.15.8/waline.js"></script>
{{ if eq .CaptchaType "math" }}
<!-- 算数验证码:弹窗模式,不影响原布局 -->
<style>
.captcha-equation-box {
display: inline-flex;
align-items: center;
background: #f0f2f5;
border: 1px solid #dcdcdc;
border-radius: 8px;
padding: 8px 8px 8px 16px;
gap: 6px;
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);
}
.math-captcha-modal-question {
font-size: 1.4rem;
font-family: "Courier New", monospace;
font-weight: 700;
color: #333;
letter-spacing: 1px;
white-space: nowrap;
}
#captcha-modal-answer {
width: 80px;
padding: 6px 10px;
text-align: center;
font-size: 1.2rem;
font-family: "Courier New", monospace;
font-weight: 600;
border: 1px solid #ccc;
border-radius: 5px;
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
background: #fff;
}
#captcha-modal-answer:focus {
border-color: #1e87f0;
box-shadow: 0 0 0 2px rgba(30,135,240,0.2);
}
/* 隐藏数字输入框的上下箭头 */
#captcha-modal-answer::-webkit-inner-spin-button,
#captcha-modal-answer::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
#captcha-modal-answer {
-moz-appearance: textfield;
}
</style>
{{ else if .RecaptchaTurnstileStyle }}
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha" async defer></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.g-recaptcha').forEach(item => {
item.disabled = true;
});
});
function onSubmit() {
document.querySelectorAll('.g-recaptcha').forEach(item => {
item.disabled = false;
})
}</script>
{{ else }}
<script src="{{.RecaptchaDomain}}/recaptcha/api.js" async defer></script>
<script>function onSubmit() {document.getElementById('form').submit();}</script>
{{ end }}
{{ if ne SentryDSN "" }}
<script
src="https://browser.sentry-cdn.com/7.37.2/bundle.tracing.replay.min.js"
integrity="sha384-o0mU2P+zsvXua0Y6f/J55PKWZt3sZCv3qDqJe6J5lZklyIQkpgjBVqNnPWYWqGhf"
crossorigin="anonymous"
></script>
<script>
Sentry.init({
dsn: "{{ SentryDSN }}",
integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
</script>
{{ end }}
<style>
.grecaptcha-badge {visibility: hidden;}
.icon-container {
display: flex;
justify-content: center;
width: 100%;
}
.icon {
margin-left: 10px;
width: 20px; /* 设置图标的宽度 */
height: auto; /* 保持图标的高度自动调整 */
}
.emoji-placeholder {
display: inline-block;
animation: jump 0.5s ease-in-out;
}
@keyframes jump {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
/* 防止 UIkit Dropbar 初始闪烁 */
.uk-dropbar {
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease, visibility 0.2s ease;
}
.uk-dropbar.uk-open {
opacity: 1;
visibility: visible;
}
a.uk-button.uk-button-link {
text-transform: none;
}
/* 移动端优化:缩小字体和间距,避免右侧空白 */
@media (max-width: 640px) {
.uk-logo {
font-size: 1rem;
}
.uk-navbar-nav a {
font-size: 0.78rem;
padding: 0 4px;
}
.emoji-placeholder {
font-size: 1rem;
}
/* 移动端用户名限宽省略,避免长英文用户名撑爆顶栏 */
.tb-nav-username {
max-width: 64px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
vertical-align: middle;
}
}
</style>
<!-- 深色模式:html[data-theme="dark"] 时覆盖 UIkit 3.3.3 编译主题的关键颜色 -->
<style>
html[data-theme="dark"] {
--tb-bg: #1a1d23;
--tb-surface: #242830;
--tb-surface-2: #2d323c;
--tb-border: #3a404c;
--tb-text: #d8dce4;
--tb-text-muted: #8b93a1;
--tb-primary: #5c8dff;
}
/* 根元素与 body 一起覆盖,确保整个视口(含内容不足一屏时的空白区)都是深色背景 */
html[data-theme="dark"],
html[data-theme="dark"] body {
background-color: var(--tb-bg) !important;
color: var(--tb-text) !important;
}
/* 通用表面容器 */
html[data-theme="dark"] .uk-card,
html[data-theme="dark"] .uk-card-default,
html[data-theme="dark"] .uk-card-secondary,
html[data-theme="dark"] .uk-dropbar,
html[data-theme="dark"] .uk-navbar-dropdown,
html[data-theme="dark"] .uk-modal-dialog,
html[data-theme="dark"] .uk-dropdown,
html[data-theme="dark"] .uk-offcanvas-bar,
html[data-theme="dark"] .uk-section,
html[data-theme="dark"] .uk-tile,
html[data-theme="dark"] .uk-panel,
html[data-theme="dark"] .uk-background-default,
html[data-theme="dark"] .uk-search-default .uk-search-input {
background-color: var(--tb-surface) !important;
color: var(--tb-text) !important;
}
html[data-theme="dark"] .uk-navbar-container {
background-color: var(--tb-surface) !important;
color: var(--tb-text) !important;
}
html[data-theme="dark"] .uk-navbar-nav > li > a,
html[data-theme="dark"] .uk-card a,
html[data-theme="dark"] .uk-link,
html[data-theme="dark"] a.uk-button-link,
html[data-theme="dark"] .uk-navbar-dropdown-nav > li > a,
html[data-theme="dark"] .uk-dropdown-nav > li > a,
html[data-theme="dark"] .uk-list a {
color: var(--tb-text) !important;
}
html[data-theme="dark"] .uk-navbar-nav > li > a:hover,
html[data-theme="dark"] .uk-card a:hover,
html[data-theme="dark"] .uk-navbar-dropdown-nav > li > a:hover,
html[data-theme="dark"] .uk-dropdown-nav > li > a:hover {
color: var(--tb-primary) !important;
}
html[data-theme="dark"] .uk-input,
html[data-theme="dark"] .uk-textarea,
html[data-theme="dark"] .uk-select {
background-color: var(--tb-surface-2) !important;
color: var(--tb-text) !important;
border-color: var(--tb-border) !important;
}
html[data-theme="dark"] .uk-input:focus,
html[data-theme="dark"] .uk-textarea:focus,
html[data-theme="dark"] .uk-select:focus {
border-color: var(--tb-primary) !important;
}
html[data-theme="dark"] .uk-text-muted,
html[data-theme="dark"] .uk-text-meta,
html[data-theme="dark"] .uk-text-secondary,
html[data-theme="dark"] .uk-text-muted a,
html[data-theme="dark"] .uk-text-small.uk-text-muted {
color: var(--tb-text-muted) !important;
}
html[data-theme="dark"] .uk-divider-icon::after,
html[data-theme="dark"] hr,
html[data-theme="dark"] .uk-table th,
html[data-theme="dark"] .uk-table td,
html[data-theme="dark"] .uk-list-divider > li:nth-child(n+2) {
border-color: var(--tb-border) !important;
}
html[data-theme="dark"] .uk-button-default {
background-color: var(--tb-surface-2) !important;
color: var(--tb-text) !important;
border-color: var(--tb-border) !important;
}
html[data-theme="dark"] .uk-modal-title,
html[data-theme="dark"] h1, html[data-theme="dark"] h2, html[data-theme="dark"] h3,
html[data-theme="dark"] h4, html[data-theme="dark"] h5, html[data-theme="dark"] h6,
html[data-theme="dark"] .uk-card-title {
color: var(--tb-text) !important;
}
html[data-theme="dark"] .uk-alert-warning,
html[data-theme="dark"] .uk-alert-danger,
html[data-theme="dark"] .uk-alert-success,
html[data-theme="dark"] .uk-alert-primary {
background-color: var(--tb-surface-2) !important;
color: var(--tb-text) !important;
}
html[data-theme="dark"] #math-captcha-modal .uk-modal-dialog {
background-color: var(--tb-surface) !important;
color: var(--tb-text) !important;
}
html[data-theme="dark"] #math-captcha-modal .captcha-equation-box {
background-color: var(--tb-surface-2) !important;
border-color: var(--tb-border) !important;
}
html[data-theme="dark"] #math-captcha-modal .math-captcha-modal-question {
color: var(--tb-text) !important;
}
html[data-theme="dark"] #math-captcha-modal #captcha-modal-answer {
background-color: var(--tb-surface) !important;
color: var(--tb-text) !important;
border-color: var(--tb-border) !important;
}
html[data-theme="dark"] #math-captcha-modal #captcha-modal-answer::placeholder {
color: var(--tb-text-muted) !important;
}
html[data-theme="dark"] #math-captcha-modal .uk-text-meta,
html[data-theme="dark"] #math-captcha-modal #captcha-modal-refresh {
color: var(--tb-text-muted) !important;
}
html[data-theme="dark"] #math-captcha-modal #captcha-modal-refresh:hover {
color: var(--tb-primary) !important;
}
html[data-theme="dark"] .uk-label {
color: #fff !important;
}
/* 自定义组件(探索页推荐卡片等)深色覆盖 */
html[data-theme="dark"] .recommend-card,
html[data-theme="dark"] .recommend-card:hover {
background-color: var(--tb-surface-2) !important;
}
html[data-theme="dark"] .explore-name a {
color: var(--tb-text) !important;
}
html[data-theme="dark"] .explore-name a:hover {
color: var(--tb-primary) !important;
}
html[data-theme="dark"] .explore-intro {
color: var(--tb-text-muted) !important;
}
/* 切换按钮图标切换 */
#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; }
</style>
<!-- 顶栏自适应:导航全部直接显示,窄屏时分级缩小字号/间距,保证英文/日文等长文字不溢出 -->
<style>
.uk-navbar-nav a {
font-size: 0.95rem;
padding: 0 10px;
}
/* 未登录移动端占位:桌面端隐藏,仅 ≤640px 显示(见下方 media 查询)。
桌面端布局与改动前完全一致,不受影响。 */
.tb-mobile-balancer { display: none; }
/* 桌面端保持 UIkit 原生居中三列布局(无需额外 CSS) */
.tb-navbar .uk-logo {
white-space: nowrap;
}
@media (max-width: 1200px) {
.uk-navbar-nav a {
font-size: 0.88rem;
padding: 0 8px;
}
}
@media (max-width: 1000px) {
.uk-navbar-nav a {
font-size: 0.82rem;
padding: 0 6px;
}
.uk-logo {
font-size: 1.1rem;
}
/* 登录用户名限宽,过长省略号截断 */
.tb-nav-username {
max-width: 110px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
vertical-align: middle;
}
}
@media (max-width: 800px) {
.uk-navbar-nav a {
font-size: 0.78rem;
padding: 0 4px;
}
.uk-logo {
font-size: 1rem;
}
.tb-nav-username {
max-width: 80px;
}
}
/* 手机端(≤640px):沿用 UIkit 桌面端 navbar 的原生三列定位。
不再重写 .uk-navbar-center / center-left / center-right 的定位,
避免 logo 被挤到下一行;底部语言与深色模式按钮仍由下方独立栏承载。 */
@media (max-width: 640px) {
.tb-navbar {
position: relative;
min-height: 56px;
overflow: visible;
}
.tb-navbar > .uk-navbar-center {
position: relative !important;
top: auto !important;
left: auto !important;
right: auto !important;
bottom: auto !important;
transform: none !important;
display: flex !important;
align-items: center;
justify-content: center;
width: 100%;
min-height: 56px;
white-space: nowrap;
flex-wrap: nowrap;
}
.tb-navbar .uk-navbar-center-left,
.tb-navbar .uk-navbar-center-right {
position: static !important;
top: auto !important;
left: auto !important;
right: auto !important;
bottom: auto !important;
transform: none !important;
display: flex !important;
align-items: center;
flex: 0 0 auto;
white-space: nowrap;
}
.tb-navbar .uk-navbar-center-left > div,
.tb-navbar .uk-navbar-center-right > div {
display: flex;
align-items: center;
white-space: nowrap;
}
.tb-navbar .uk-navbar-nav {
display: flex;
flex-wrap: nowrap;
flex: 0 0 auto;
}
.tb-navbar .uk-navbar-nav > li > a,
.tb-navbar .uk-logo {
white-space: nowrap;
}
/* 未登录:移动端显示与「我的」等宽的隐藏占位,使左栏「探索+占位」与
右栏「注册/登录」两项等宽,logo 真正居中,左右视觉平衡 */
.tb-mobile-balancer { display: block; }
}
</style>
<!-- 标签样式的按钮(管理页置顶等):
尺寸完全继承 .uk-label(font-size/padding/line-height/text-transform 等),
仅覆盖 button 元素特有的默认样式(边框/字体族/光标等),保证与相邻 span 标签完全一致。 -->
<style>
.tb-label-btn {
border: none !important;
cursor: pointer;
font-family: inherit;
margin: 0;
vertical-align: middle;
outline: none;
background-image: none;
box-sizing: border-box;
-webkit-appearance: none;
appearance: none;
}
.tb-label-btn:hover {
opacity: 0.85;
}
/* 公开页置顶小图标:仅一个图钉,不占行高、不影响原布局 */
.tb-pin-flag {
font-size: 0.9em;
margin-right: 4px;
vertical-align: -1px;
cursor: default;
}
</style>
<!-- Markdown 渲染排版(问题/回答/追问正文,左对齐以保留排版格式) -->
<style>
/* 纯文本列表:保留换行与连续空格(pre-wrap),不渲染 Markdown 大字号/彩色 */
.tb-preline {
white-space: pre-wrap;
}
/* 纯文本列表:超过 5 行截断并显示省略号(不足 5 行则完整显示) */
.tb-clamp-5 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
overflow: hidden;
}
.md-body {
line-height: 1.7;
text-align: left;
word-break: break-word;
}
.md-body p { margin: 0.5em 0; }
.md-body h1, .md-body h2, .md-body h3,
.md-body h4, .md-body h5, .md-body h6 {
margin: 0.8em 0 0.4em;
font-weight: 600;
line-height: 1.4;
}
.md-body ul, .md-body ol { padding-left: 1.6em; margin: 0.5em 0; }
.md-body li { margin: 0.2em 0; }
.md-body blockquote {
border-left: 3px solid #d3d1c7;
margin: 0.6em 0;
padding: 0.2em 1em;
color: #5f5e5a;
}
.md-body pre {
background: #f5f5f5;
padding: 0.8em 1em;
border-radius: 6px;
overflow-x: auto;
margin: 0.6em 0;
}
.md-body code {
background: #f0f0f0;
padding: 0.1em 0.35em;
border-radius: 4px;
font-size: 0.9em;
}
.md-body pre code { background: none; padding: 0; }
.md-body img { max-width: 100%; height: auto; }
.md-body table { border-collapse: collapse; margin: 0.6em 0; }
.md-body th, .md-body td {
border: 1px solid #ddd;
padding: 0.4em 0.8em;
}
.md-body hr { margin: 1em 0; }
html[data-theme="dark"] .md-body blockquote {
border-color: #3a404c;
color: #8b93a1;
}
html[data-theme="dark"] .md-body pre,
html[data-theme="dark"] .md-body code { background: #2d323c; }
html[data-theme="dark"] .md-body th,
html[data-theme="dark"] .md-body td { border-color: #3a404c; }
</style>
<!-- 投票结果占比条:UIkit 3 已无 .uk-progressbar 类(那是 UIkit 2 的),
模板用 <div class="uk-progress"><div class="uk-progressbar"> 结构,需补一套填充样式,
否则内层填充无高度/背景,所有占比条都只剩外层灰色轨道,看起来一模一样。 -->
<style>
.uk-progress {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
height: 10px;
margin: 0;
border: none;
border-radius: 6px;
background-color: #e5e5e5;
overflow: hidden;
box-shadow: none;
}
.uk-progressbar {
display: block;
height: 100%;
background-color: #1e87f0;
border-radius: 6px 0 0 6px;
transition: width 0.3s ease;
}
html[data-theme="dark"] .uk-progress {
background-color: var(--tb-border);
}
html[data-theme="dark"] .uk-progressbar {
background-color: var(--tb-primary);
}
</style>
<!-- 移动端底部操作栏:固定底栏承载语言切换与深色模式,桌面端隐藏 -->
<style>
#tb-mobile-utils {
display: none;
}
@media (max-width: 640px) {
/* 顶栏不再容纳语言/深色按钮,恢复原有导航空间 */
.tb-nav-utils {
display: none !important;
}
/* 手机端隐藏登录用户名(左边仅保留"探索"),保证 logo 居中、
两侧各不超过 2 个短按钮,避免用户名宽度挤压布局 */
.tb-nav-username {
display: none !important;
}
/* 固定底部操作栏 */
#tb-mobile-utils {
display: flex;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
justify-content: center;
align-items: center;
gap: 10px;
padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
background: rgba(255, 255, 255, 0.92);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
}
#tb-mobile-utils .uk-select {
width: 110px;
}
html[data-theme="dark"] #tb-mobile-utils {
background: rgba(36, 40, 48, 0.92);
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.4);
}
/* 给页面底部预留空间,避免页脚内容被固定底栏遮挡 */
body {
padding-bottom: 56px;
}
}
</style>
<!-- Commit 6d0a3ad -->
<meta property="og:site_name" content="{{ t .LangCode "site.title" }}" />
<meta property="og:type" content="website" />
<meta property="og:title" content="{{ if .IsUserPage }}{{ .PageUser.Name }} - {{ t .LangCode "site.title" }}{{ else }}{{ t .LangCode "site.title" }}{{ end }}" />
<meta property="og:url" content="{{ .ExternalURL }}{{ .CurrentURI }}" />
<meta property="og:description" content="{{ if .IsUserPage }}{{ .PageUser.Name }} - {{ t .LangCode "site.description" }}{{ else }}{{ t .LangCode "site.description" }}{{ end }}" />
{{ if .NoIndex }}<meta name="robots" content="noindex">{{ end }}
{{ if .IsUserPage }}
<meta property="og:image" content="{{ .PageUser.Avatar }}?og-image=1" />
<meta property="og:image:alt" content="{{ .PageUser.Name }}" />
<meta itemprop="image" content="{{ .PageUser.Avatar }}?og-image=1"/>
<meta name="msapplication-TileImage" content="{{ .PageUser.Avatar }}"/>
<link rel="shortcut icon" href="{{ .PageUser.Avatar }}?og-image=1"/>
<link rel="apple-touch-icon" href="{{ .PageUser.Avatar }}?og-image=1"/>
<link rel="icon" sizes="192x192" href="{{ .PageUser.Avatar }}?og-image=1">
<meta name="twitter:image:src" content="{{ .PageUser.Avatar }}?og-image=1" />
{{ else }}
<meta itemprop="image" content="https://r2.337845818.best/tamabox/tamabox_low.png"/>
<meta name="msapplication-TileImage" content="https://r2.337845818.best/tamabox/tamabox_low.png"/>
<link rel="shortcut icon" href="https://r2.337845818.best/tamabox/tamabox_low.png" />
<link rel="apple-touch-icon" href="https://r2.337845818.best/tamabox/tamabox_low.png"/>
<link rel="icon" sizes="192x192" href="https://r2.337845818.best/tamabox/tamabox_low.png">
<meta name="twitter:image:src" content="https://r2.337845818.best/tamabox/tamabox_low.png" />
{{ end }}
<!-- Google and Bing -->
<meta name="google-site-verification" content="fEPwRRJfiGsgwc5SINBQwZhPK8F0cwRaOvRx0YRurI4" />
<meta name="msvalidate.01" content="2F185E8E63C93635F50B3574F3B31E42" />
<script defer src="https://mami.tama.guru/tamami.js" data-website-id="c42ebe8b-9281-4da0-83a4-8f18b3e710fe"></script>
</head>
<body>
<script>
// 二维码降级渲染(P0-②):easy.qrcode 的 logo(用户头像)/ 背景图加载失败或极慢时,
// 库内部仅 console.error 且无降级 → 二维码区域会永久空白。
// 策略:未知状态的远程图先不画(基础码立即可扫)→ 后台探针(Image 预载)→
// 判定完成后再重绘带 logo/背景的升级版;失败则保持基础码,绝不空白。
// 同会话记忆:探针结果按 URL 记忆(good/bad),编辑页拖动滑块高频重绘时零等待、不闪降级。
// 使用:window.tbRenderQR(el, function(){ return options; }) // options 含完整 logo/backgroundImage
window.tbRenderQR = function (el, getOptions) {
if (!el || typeof QRCode === 'undefined') return;
var opts = getOptions();
function remoteOf(o, key) {
var v = o[key];
return (typeof v === 'string' && v.indexOf('http') === 0) ? v : '';
}
var good = window._tbQrGood || (window._tbQrGood = {}); // URL -> true(可 CORS 加载)
var bad = window._tbQrBad || (window._tbQrBad = {}); // URL -> true(加载失败)
// 每层状态:''=无需, 1=已知可用, 0=已知失败, null=未知需探针
function layerState(url) { return !url ? '' : (good[url] ? 1 : (bad[url] ? 0 : null)); }
var layers = [
{ url: remoteOf(opts, 'logo'), st: null, present: function (o, on) {
if (on) { /* 保留 */ } else { o.logo = undefined; o.logoWidth = undefined; o.logoHeight = undefined; o.logoBackgroundTransparent = undefined; }
} },
{ url: remoteOf(opts, 'backgroundImage'), st: null, present: function (o, on) {
if (on) { /* 保留 */ } else { o.backgroundImage = undefined; o.backgroundImageAlpha = undefined; }
} }
];
var needProbe = false;
for (var i = 0; i < layers.length; i++) {
layers[i].st = layerState(layers[i].url);
if (layers[i].st === null) needProbe = true;
}
function paint(states) {
var o = {};
for (var k in opts) o[k] = opts[k];
for (var j = 0; j < layers.length; j++) layers[j].present(o, states[j] === 1);
el.innerHTML = '';
new QRCode(el, o);
}
// 已知可用层立即带上;未知层先省略 → 立即绘制(logo 记忆命中时不会闪降级)
var initStates = [];
for (var n = 0; n < layers.length; n++) initStates.push(layers[n].st === 1 ? 1 : 0);
paint(initStates);
if (!needProbe) return;
// 后台探针未知层;全部判定后若有变化再重绘一次升级版
var pending = 0, cross = opts.crossOrigin || 'anonymous';
layers.forEach(function (L) {
if (L.st !== null) return;
pending++;
var im = new Image();
im.onload = function () { good[L.url] = true; L.st = 1; onDone(); };
im.onerror = function () { bad[L.url] = true; L.st = 0; onDone(); };
if (cross) im.crossOrigin = cross;
im.src = L.url;
});
function onDone() {
if (--pending > 0) return;
var finalStates = [];
for (var m = 0; m < layers.length; m++) finalStates.push(layers[m].st === 1 ? 1 : 0);
var changed = false;
for (var q = 0; q < finalStates.length; q++) if (finalStates[q] !== initStates[q]) changed = true;
if (changed) paint(finalStates);
}
// 超时保险:个别挂起连接永不回调时,3s 后按当前已知状态收尾(基础码早已可扫)
setTimeout(function () { if (pending > 0) { pending = 0; onDone(); } }, 3000);
};
</script>
<nav class="uk-navbar-container uk-margin tb-navbar" uk-navbar>
<div class="uk-navbar-center">
<div class="uk-navbar-center-left">
<div>
<ul class="uk-navbar-nav">
<!-- 探索链接,始终显示 -->
<li><a href="/explore">{{t .LangCode "nav.explore"}}</a></li>
{{ if .IsLogged }}
<!-- 我的:进入自己的提问箱主页(与右侧「提问/设置」形成左右均衡) -->
<li><a href="/_/{{ .LoggedUser.Domain }}">{{t .LangCode "nav.mine"}}</a></li>
{{ else }}
<!-- 移动端占位:未登录时左栏仅「探索」一项,与右栏「注册/登录」两项不等宽,
导致 logo 整体被拉向左(偏左)。移动端(≤640px)显示一个与「我的」等宽的隐藏占位,
使左右视觉平衡;桌面端由 .tb-mobile-balancer 隐藏,保持原布局完全不变。 -->
<li class="tb-mobile-balancer" aria-hidden="true"><a tabindex="-1" style="visibility:hidden">{{t .LangCode "nav.mine"}}</a></li>
{{ end }}
</ul>
</div>
</div>
<a class="uk-navbar-item uk-logo" href="/"><span class="emoji-placeholder" aria-label="random animal">🐺</span>{{t .LangCode "site.logoSuffix"}}</a>
<div class="uk-navbar-center-right">
<div>
{{ if .IsLogged }}
<ul class="uk-navbar-nav">
<li><a href="/user/questions">{{t .LangCode "nav.ask"}}</a></li>
</ul>
<ul class="uk-navbar-nav">
<li><a href="/user/profile">{{t .LangCode "nav.settings"}}</a></li>
</ul>
{{ else}}
<ul class="uk-navbar-nav">
<li><a href="/register">{{t .LangCode "nav.register"}}</a></li>
</ul>
<ul class="uk-navbar-nav">
<li><a href="/login">{{t .LangCode "nav.login"}}</a></li>
</ul>
{{ end }}
{{ if not .IsUserPage }}
<!-- 语言切换(紧凑图标,点击展开;移动端隐藏,见底部操作栏) -->
<ul class="uk-navbar-nav tb-nav-utils">
<li>
<a href="#" aria-label="{{t .LangCode "common.lang"}}" title="{{t .LangCode "common.lang"}}">🌐</a>
<div class="uk-navbar-dropdown" uk-drop="mode: click; pos: bottom-right">
<ul class="uk-nav uk-navbar-dropdown-nav">
{{ range .SupportedLangs }}
<li class="{{ if eq .Code $.LangCode }}uk-active{{ end }}">
<a href="/i18n/{{ .Code }}?to={{ urlquery $.CurrentURI }}">{{ .Name }}</a>
</li>
{{ end }}
</ul>
</div>
</li>
</ul>
{{ end }}
<!-- 深色模式切换(紧凑图标;移动端隐藏,见底部操作栏) -->
<ul class="uk-navbar-nav tb-nav-utils">
<li>
<a href="#" id="theme-toggle" title="{{t .LangCode "common.theme"}}" aria-label="{{t .LangCode "common.theme"}}">
<span class="tb-icon-light">🌙</span><span class="tb-icon-dark">☀️</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</nav>
<script>
document.addEventListener("DOMContentLoaded", function() {
const emojis = ["🐺","🐗","🐻","🐨","🐼","🐹","🐭","🐰","🦊","🐮","🐷","🐽","🐸","🐒","🦍","🐔","🐧","🐦","🐤","🐦‍⬛","🦅","🦉","🦇","🐴","🦄","🐝","🦎","🦖","🦕","🐙","🦑","🦐","🦞","🦀","🐡","🐠","🐟","🐬","🐳","🐋","🦈","🐊","🐅","🐆","🦓","🦍","🦧","🐘","🦛","🦏","🐪","🐫","🦒","🦘","🐃","🐂","🐄","🐎","🐖","🐏","🐑","🦙","🐐","🦌","🐕","🐩","🦮","🐕‍🦺","🐈","🐋","🐳","🐬","🐉","🐲"];
const placeholder = document.querySelector('.emoji-placeholder');
if (placeholder) {
placeholder.textContent = emojis[Math.floor(Math.random() * emojis.length)];
}
});
</script>
<!-- 移动端固定底部操作栏:语言切换 + 深色模式(桌面端隐藏) -->
<div id="tb-mobile-utils">
{{ if not .IsUserPage }}
<select id="mobile-lang-select" class="uk-select uk-form-small" aria-label="{{t .LangCode "common.lang"}}">
{{ range .SupportedLangs }}
<option value="{{ .Code }}" {{ if eq .Code $.LangCode }}selected{{ end }}>{{ .Name }}</option>
{{ end }}
</select>
{{ end }}
<button type="button" id="theme-toggle-mobile" class="uk-button uk-button-default uk-button-small"
title="{{t .LangCode "common.theme"}}" aria-label="{{t .LangCode "common.theme"}}">
<span class="tb-icon-light">🌙</span><span class="tb-icon-dark">☀️</span>
</button>
</div>
<script>
(function() {
// 深色模式:读取 cookie 应用主题,按钮切换并持久化
var THEME_COOKIE = 'theme';
function getTheme() {
var m = document.cookie.match(/(?:^|;\s*)theme=([^;]*)/);
return m ? m[1] : '';
}
function applyTheme(theme) {
if (theme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.removeAttribute('data-theme');
}
}
function toggleTheme() {
var next = getTheme() === 'dark' ? 'light' : 'dark';
applyTheme(next);
var expires = new Date(Date.now() + 365 * 24 * 3600 * 1000).toUTCString();
document.cookie = THEME_COOKIE + '=' + next + '; path=/; max-age=' + (365 * 24 * 3600) + '; SameSite=Lax';
}
// 首屏前应用,避免闪烁
applyTheme(getTheme());
document.addEventListener('DOMContentLoaded', function() {
// 桌面顶栏按钮与移动端底部按钮都绑定
var buttons = document.querySelectorAll('#theme-toggle, #theme-toggle-mobile');
buttons.forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.preventDefault();
toggleTheme();
});
});
// 移动端底部语言切换:onchange 跳转到 /i18n/{code},保留当前页面位置
var langSel = document.getElementById('mobile-lang-select');
if (langSel) {
langSel.addEventListener('change', function() {
if (!this.value) return;
var to = window.location.pathname + window.location.search;
window.location.href = '/i18n/' + encodeURIComponent(this.value) + '?to=' + encodeURIComponent(to);
});
}
});
})();
</script>
{{ if .IsPixel }}
<div>
{{ else }}
<div class="uk-container uk-container-xsmall">
{{ end }}
{{ if eq .CaptchaType "math" }}
<!-- 算数验证码弹窗 --><div id="math-captcha-modal" uk-modal>
<div class="uk-modal-dialog uk-modal-body uk-text-center">
<h3 class="uk-modal-title">{{t .LangCode "captcha.title"}}</h3>
<p class="uk-text-meta">{{t .LangCode "captcha.hint"}}</p>
<div class="captcha-equation-box">
<span id="captcha-modal-question" class="math-captcha-modal-question">{{t .LangCode "captcha.loading"}}</span>
<input type="number" id="captcha-modal-answer" placeholder="?" autocomplete="off" inputmode="numeric">
</div>
<div style="margin-top:6px;">
<button type="button" class="uk-button uk-button-text uk-text-small" id="captcha-modal-refresh">{{t .LangCode "captcha.refresh"}}</button>
</div>
<div id="captcha-modal-error" class="uk-text-danger uk-text-small" style="display:none;min-height:20px;"></div>
<div class="uk-margin-small-top">
<button type="button" class="uk-button uk-button-primary" id="captcha-modal-submit">{{t .LangCode "captcha.submit"}}</button>
<button type="button" class="uk-button uk-button-default uk-modal-close">{{t .LangCode "common.cancel"}}</button>
</div>
</div>
</div>
<script>
var CAPTCHA_I18N = {
loading: "{{t .LangCode "captcha.loading"}}",
loadFailed: "{{t .LangCode "captcha.loadFailed"}}",
loadRetry: "{{t .LangCode "captcha.loadRetry"}}",
inputAnswer: "{{t .LangCode "captcha.inputAnswer"}}",
wrongAnswer: "{{t .LangCode "captcha.wrongAnswer"}}",
verifyFailed: "{{t .LangCode "captcha.verifyFailed"}}",
needPassword: "{{t .LangCode "ask.needPassword"}}",
needEmail: "{{t .LangCode "ask.pleaseEmail"}}"
};
</script>
<script>
(function() {
var captchaId = '';
var pendingForm = null;
var pendingNeedPassword = false;
var verifying = false;
function fetchCaptcha() {
var qEl = document.getElementById('captcha-modal-question');
var errEl = document.getElementById('captcha-modal-error');
var ansEl = document.getElementById('captcha-modal-answer');
qEl.textContent = CAPTCHA_I18N.loading;
fetch('/api/captcha/generate')
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.code === 0 && data.data) {
captchaId = data.data.id;
qEl.textContent = data.data.question;
if (errEl) errEl.style.display = 'none';
if (ansEl) { ansEl.value = ''; ansEl.focus(); }
} else {
qEl.textContent = CAPTCHA_I18N.loadFailed;
}
})
.catch(function() {
qEl.textContent = CAPTCHA_I18N.loadRetry;
});
}
function getCSRFToken() {
var input = document.querySelector('input[name="_csrf"]');
return input ? input.value : '';
}
function doSubmit(form) {
// 将 captcha 答案填入隐藏字段
var ans = document.getElementById('captcha-modal-answer').value.trim();
var hidden = form.querySelector('[name="g-recaptcha-response"]');
if (hidden) hidden.value = captchaId + ':' + (ans || '0');
UIkit.modal('#math-captcha-modal').hide();
// 处理 AskPassword(提问箱密码)
if (pendingNeedPassword) {
UIkit.modal.prompt(CAPTCHA_I18N.needPassword, '', 'password')
.then(function(password) {
if (password) {
var pwInput = document.getElementById('ask_password_input');
if (pwInput) pwInput.value = password;
form.submit();
}
});
} else {
form.submit();
}
}
// 验证按钮
document.getElementById('captcha-modal-submit').addEventListener('click', function() {
if (verifying) return;
var ans = document.getElementById('captcha-modal-answer').value.trim();
if (!ans) {
var errEl = document.getElementById('captcha-modal-error');
errEl.textContent = CAPTCHA_I18N.inputAnswer;
errEl.style.display = 'block';
return;
}
verifying = true;
fetch('/api/captcha/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCSRFToken()
},
body: JSON.stringify({ id: captchaId, answer: parseInt(ans, 10) })
})
.then(function(r) { return r.json(); })
.then(function(data) {
verifying = false;
if (data.correct) {
if (pendingForm) doSubmit(pendingForm);
} else {
var errEl = document.getElementById('captcha-modal-error');
errEl.textContent = CAPTCHA_I18N.wrongAnswer;
errEl.style.display = 'block';
fetchCaptcha(); // 自动刷新题目
}
})
.catch(function() {
verifying = false;
var errEl = document.getElementById('captcha-modal-error');
errEl.textContent = CAPTCHA_I18N.verifyFailed;
errEl.style.display = 'block';
fetchCaptcha();
});
});
// 回车提交
document.getElementById('captcha-modal-answer').addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
document.getElementById('captcha-modal-submit').click();
}
});
// 刷新按钮
document.getElementById('captcha-modal-refresh').addEventListener('click', fetchCaptcha);
// 拦截所有带 class="math-captcha-trigger" 的按钮
document.addEventListener('click', function(e) {
var btn = e.target.closest('.math-captcha-trigger');
if (!btn) return;
e.preventDefault();
var form = btn.closest('form');
if (!form) return;
pendingForm = form;
// 检查是否需要提问密码
pendingNeedPassword = !!(form.querySelector('#ask_password_input'));
// 邮箱校验(提问页面)
var notifyCb = document.getElementById('notify-checkbox');
var emailInput = document.getElementById('email-input');
if (notifyCb && notifyCb.checked && emailInput && emailInput.value.trim() === '') {
UIkit.modal.alert(CAPTCHA_I18N.needEmail);
return;
}
fetchCaptcha();
UIkit.modal('#math-captcha-modal').show();
});
})();
</script>
{{ end }}