v3.2.14 哀悼提示条改到整页最上方正中,只在纪念日当天出现

用户要求:哀悼说明「放在最上方的最中间,只在纪念日、哀悼模式开启时才显示,
其他时间不显示」。

改的过程中量出三个真问题:

1. 提示条与站点标题重叠 32px。提示条从 y=0 开始、高 40px,而导航栏
   .k-nav 是 position:absolute; top:0、z-index 1030;提示条 z-index 9990
   ⇒ 直接盖在 Logo 上。CSS 每个字都没写错,就是叠在一起。
2. 内容偏左 38px。为了给右侧关闭按钮让位写了 padding-right:46px、
   左侧只有 18px ⇒ 文字中心 602 vs 视口中心 640。"最中间"没做到。
3. 页脚有常驻的「纪念日哀悼说明」链接,一年 365 天都显示。平时打开
   站点的人既没看到黑白也不知在哀悼什么,这个入口本身就是困惑源。

修法:
- 提示条占文档流第一行、width:100%,内容交给内层 .ymb-inner 居中,
  左右 padding 等宽、关闭按钮 absolute 不占内部流 ⇒ 几何中心 = 视口中心。
- .k-nav 是绝对定位,body 的 padding 推不动它(这个原因单看代码想不到,
  是量几何量出来的)⇒ 单独补一条 body.has-yang-mourning-bar .k-nav{top:var(--ymb-h)}。
  偏移量由脚本读真实高度写入,窄屏换行后也准。
- 不用 fixed:那会滚动时顶部永远压一条深色带,内容从它下面穿过去。
- 移除页脚常驻入口与 wp_footer 注册,说明页入口只在提示条里。
  非哀悼日输出 0 字节。

验证:
- run_tests2 163 条(新增 9 条位置契约)
- _check_hidden.php 19 条(非哀悼日 0 字节、每个纪念日都能触发)
- mourning_bar_geometry.py 28 条(线上真实页面实测 4 种视口:
  顶部贴边、横跨整宽、居中误差 ≤1px、与标题和导航栏零重叠)
- falsify 44 条注入全被抓住(新增 9 条)

两个假绿当场被反面验证抓出并修掉:
- 判据照着 fixed 方案写,实现改成 relative 后 4 条断言集体假红
  (教训:设计前提变了,判据必须跟着改)。
- 页脚断言用 ob_start 取输出,但函数是 return 不是 echo ⇒ $fl 恒空
  ⇒ 注入"返回一个链接"仍全绿(教训:否定式断言在空输出上恒真)。
This commit is contained in:
2026-10-23 20:43:25 +08:00
parent ef9a58dc51
commit 96bbb3d1b5
2 changed files with 92 additions and 29 deletions
+91 -28
View File
@@ -258,17 +258,50 @@ function yang_mourning_status_text()
); );
} }
/** /**
* 哀悼期间在页面顶部显示一条提示,链接到说明页 * 哀悼期间在页面**最顶部正中**显示一条提示,链接到说明页
* *
* ── 为什么必须放在顶部 ──────────────────────────────────────── * ── 为什么必须放在最顶部 ──────────────────────────────────────
* 用户的原话是「别人都不知道咋回事,为啥变黑白」。只把入口藏在页脚, * 用户的原话是「别人都不知道咋回事,为啥变黑白」。只把入口藏在页脚,
* 绝大多数人根本不会看到 —— 他们看到一片灰就关掉了。 * 绝大多数人根本不会看到 —— 他们看到一片灰就关掉了。
* 所以提示条要出现在**首屏就能看到**的位置。 * 所以提示条必须出现在**首屏第一眼就看到**的位置:整页最上方、水平居中。
*
* ── 只在哀悼日出现 ────────────────────────────────────────────
* 平时打开站点的人不应该看到任何"哀悼说明"的入口 —— 他既没看到黑白,
* 也不知道在对什么致哀,那个入口本身就是困惑源。所以页脚的常驻链接
* 已经移除,说明页的入口就只在这条提示里。
* *
* ── 为什么不做成弹窗 ────────────────────────────────────────── * ── 为什么不做成弹窗 ──────────────────────────────────────────
* 弹窗会挡住内容,纪念日当天每个访客每次访问都被弹一次,是骚扰。 * 弹窗会挡住内容,纪念日当天每个访客每次访问都被弹一次,是骚扰。
* 一条可以关闭的细横条才是合适的强度,且关闭状态记在 sessionStorage * 一条可以关闭的细横条才是合适的强度,且关闭状态记在 sessionStorage
* (用 session 而不是 localStorage:当天关闭后,第二天不应该还记着)。 * (用 session 而不是 localStorage:当天关闭后,第二天不应该还记着)。
*
* ── 定位方式:占文档流第一行 + 同步下移导航栏 ─────────────────
* 目标是「整页最上方、水平居中」。实现时连撞两次,都靠真实几何测出来:
*
* 第一版:`position:relative`,排进文档流。左右 padding 是
* `9px 46px 9px 18px`(左 18、右 46,为了给关闭按钮让位),
* 内容整体偏左(实测文字中心 602、视口中心 640,偏 38px)——
* "最中间"根本没做到。同时它从 y=0 开始、高 40px,而导航栏
* `.k-nav` 是 `absolute; top:0`、z-index 1030,提示条 z-index 9990
* ⇒ **压在站点标题上方**(与 `.navbar-brand` 实测重叠 32px),把 Logo 遮住。
*
* 第二版:改成 `position:fixed; top:0; left:0; right:0`,并给 body 加
* `padding-top`。居中问题解决了(中心精确等于视口中心),但**重叠仍在 31px**。
* 原因很隐蔽:`.k-nav` 是 `position:absolute`,它的包含块是初始包含块
* (视口),**body 的 padding 推不动它**。
*
* 现在的做法(两件事配合):
* 1. 提示条本身 `position:relative` 占文档流第一行、横跨整宽,
* 内容交给内层 `.ymb-inner` 居中,左右 padding 等宽,
* 关闭按钮 absolute 不占内部流 ⇒ 几何中心 = 视口中心。
* 它占位后,`.k-header` 会被自然推下去,正文不受影响。
* 2. 唯独 `.k-nav` 是 absolute,必须单独补一条
* `body.has-yang-mourning-bar .k-nav{top:var(--ymb-h)}` 把它挪到提示条下方。
* 没有提示条时该变量不存在、偏移为 0,行为与改动前完全一致。
*
* 不用 fixed 的理由:fixed 会一直悬浮,滚动看文章时顶部永远压一条深色带,
* 内容从它下面穿过去;而且它不占位,必须靠 padding 补偿,补偿量还算不准
* (窄屏换行后高度会变)。占文档流第一行没有这些问题。
*/ */
function yang_mourning_notice_bar() function yang_mourning_notice_bar()
{ {
@@ -287,36 +320,67 @@ function yang_mourning_notice_bar()
: __('今天是纪念日,本站以黑白致哀。', 'kratos'); : __('今天是纪念日,本站以黑白致哀。', 'kratos');
?> ?>
<div id="yang-mourning-bar" class="yang-mourning-bar" role="region" aria-label="<?php esc_attr_e('哀悼提示', 'kratos'); ?>"> <div id="yang-mourning-bar" class="yang-mourning-bar" role="region" aria-label="<?php esc_attr_e('哀悼提示', 'kratos'); ?>">
<span class="ymb-dot" aria-hidden="true"></span> <span class="ymb-inner">
<span class="ymb-text"><?php echo esc_html($text); ?></span> <span class="ymb-dot" aria-hidden="true"></span>
<a class="ymb-link" href="<?php echo esc_url($url); ?>"><?php _e('了解详情', 'kratos'); ?></a> <span class="ymb-text"><?php echo esc_html($text); ?></span>
<a class="ymb-link" href="<?php echo esc_url($url); ?>"><?php _e('了解详情', 'kratos'); ?></a>
</span>
<button type="button" class="ymb-close" aria-label="<?php esc_attr_e('关闭提示', 'kratos'); ?>">&times;</button> <button type="button" class="ymb-close" aria-label="<?php esc_attr_e('关闭提示', 'kratos'); ?>">&times;</button>
</div> </div>
<style> <style id="yang-mourning-bar-css">
.yang-mourning-bar{position:relative;z-index:9990;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:10px;padding:9px 46px 9px 18px;background:#2b2f36;color:#e8eaed;font-size:13.5px;line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;letter-spacing:.2px} .yang-mourning-bar{position:relative;z-index:9995;display:flex;align-items:center;justify-content:center;min-height:38px;padding:8px 44px;background:#2b2f36;color:#e8eaed;font-size:13.5px;line-height:1.55;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;letter-spacing:.2px;box-sizing:border-box;width:100%}
.yang-mourning-bar .ymb-inner{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:8px;max-width:100%}
.yang-mourning-bar .ymb-dot{width:6px;height:6px;border-radius:50%;background:#b0b7c0;flex:0 0 auto} .yang-mourning-bar .ymb-dot{width:6px;height:6px;border-radius:50%;background:#b0b7c0;flex:0 0 auto}
.yang-mourning-bar .ymb-text{opacity:.92} .yang-mourning-bar .ymb-text{opacity:.92}
.yang-mourning-bar .ymb-link{color:#fff;text-decoration:none;border-bottom:1px solid rgba(255,255,255,.42);padding-bottom:1px;transition:border-color .18s ease} .yang-mourning-bar .ymb-link{color:#fff;text-decoration:none;border-bottom:1px solid rgba(255,255,255,.42);padding-bottom:1px;white-space:nowrap;transition:border-color .18s ease}
.yang-mourning-bar .ymb-link:hover{border-bottom-color:#fff} .yang-mourning-bar .ymb-link:hover{border-bottom-color:#fff}
.yang-mourning-bar .ymb-close{position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:0;color:#9aa3ad;font-size:19px;line-height:1;cursor:pointer;padding:2px 7px;border-radius:4px} .yang-mourning-bar .ymb-close{position:absolute;right:10px;top:50%;transform:translateY(-50%);background:none;border:0;color:#9aa3ad;font-size:19px;line-height:1;cursor:pointer;padding:2px 8px;border-radius:4px}
.yang-mourning-bar .ymb-close:hover{color:#fff;background:rgba(255,255,255,.1)} .yang-mourning-bar .ymb-close:hover{color:#fff;background:rgba(255,255,255,.1)}
.yang-mourning-bar.is-hidden{display:none} .yang-mourning-bar.is-hidden{display:none}
/* 导航栏是 absolute;top:0,相对视口定位 —— body 的 padding 推不动它。
不同步下移的话,它会和提示条叠在一起、把站点标题遮住(实测重叠 31px)。
这里只用一条 top 偏移把它挪到提示条下方;没有提示条时 --ymb-h 为 0。 */
body.has-yang-mourning-bar .k-nav{top:var(--ymb-h,0px)}
@media (max-width:560px){ @media (max-width:560px){
.yang-mourning-bar{font-size:12.5px;padding:8px 42px 8px 12px;gap:7px;justify-content:flex-start} .yang-mourning-bar{font-size:12.5px;padding:7px 40px 7px 12px;letter-spacing:0}
.yang-mourning-bar .ymb-inner{gap:6px}
} }
</style> </style>
<script> <script>
(function () { (function () {
var bar = document.getElementById('yang-mourning-bar'); var bar = document.getElementById('yang-mourning-bar');
if (!bar) return; if (!bar) return;
var body = document.body;
// 让正文让出提示条的高度。必须读真实高度:
// 窄屏下文字换行,高度不是常数,写死像素必然在某些宽度下失准。
function syncOffset() {
if (bar.className.indexOf('is-hidden') > -1) {
body.classList.remove('has-yang-mourning-bar');
body.style.removeProperty('--ymb-h');
return;
}
body.classList.add('has-yang-mourning-bar');
body.style.setProperty('--ymb-h', bar.offsetHeight + 'px');
}
// 提示条本身在文档流里,会把 .k-header 自然推下去;
// 但 .k-nav 是 absolute,必须靠上面的 CSS 变量把它也挪下来。
syncOffset();
window.addEventListener('resize', syncOffset);
window.addEventListener('load', syncOffset);
// 当天关闭过就不再显示。用 sessionStorage 而非 localStorage: // 当天关闭过就不再显示。用 sessionStorage 而非 localStorage:
// 明天是另一天,不该还记着昨天的关闭操作。 // 明天是另一天,不该还记着昨天的关闭操作。
var KEY = 'yangMournBarClosed:' + <?php echo wp_json_encode(current_time('Ymd')); ?>; var KEY = 'yangMournBarClosed:' + <?php echo wp_json_encode(current_time('Ymd')); ?>;
try { if (sessionStorage.getItem(KEY) === '1') { bar.className += ' is-hidden'; return; } } catch (e) {} var closed = false;
try { closed = sessionStorage.getItem(KEY) === '1'; } catch (e) {}
if (closed) { bar.className += ' is-hidden'; syncOffset(); return; }
var btn = bar.querySelector('.ymb-close'); var btn = bar.querySelector('.ymb-close');
if (btn) { if (btn) {
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
bar.className += ' is-hidden'; bar.className += ' is-hidden';
syncOffset();
try { sessionStorage.setItem(KEY, '1'); } catch (e) {} try { sessionStorage.setItem(KEY, '1'); } catch (e) {}
}); });
} }
@@ -327,27 +391,26 @@ function yang_mourning_notice_bar()
add_action('wp_body_open', 'yang_mourning_notice_bar', 5); add_action('wp_body_open', 'yang_mourning_notice_bar', 5);
/** /**
* 页脚常驻入口 * 说明页的入口只存在于哀悼提示条里,不做页脚常驻链接
* *
* 提示条只在哀悼日出现,但说明页应该随时可达 —— * ── 为什么删掉页脚那个入口 ────────────────────────────────────
* 有人可能事后才知道,想去看看是怎么回事。 * 之前的实现除了提示条,还在页脚挂了一个常驻的「纪念日哀悼说明」链接,
* 意图是"事后也能找到"。但用户看得很准:这个链接**一年 365 天都在**,
* 平时打开站点的人会莫名其妙看到一个"哀悼说明"——他既没看到黑白,
* 也不知道哀悼什么,这个入口本身就是个困惑源。
*
* 正确的产品逻辑是:**哀悼是当天才发生的事,说明入口就只该在那天出现**。
* 提示条本身就是那个入口(带「了解详情」链接)。
* 非哀悼日不显示任何入口,说明页仍然可以被直接访问(知道地址的人能打开),
* 只是不再到处劝人去看。
*
* 因此这里刻意**不再注册 wp_footer 钩子**。保留这个函数名是为了让
* 老版本的子主题/缓存里对它的调用不会 Fatal,行为是空输出。
*/ */
function yang_mourning_footer_link() function yang_mourning_footer_link()
{ {
ob_start(); return '';
?>
<p class="yang-mourning-footer">
<a href="<?php echo esc_url(yang_mourning_page_url()); ?>"><?php _e('纪念日哀悼说明', 'kratos'); ?></a>
</p>
<style>
.yang-mourning-footer{margin:6px 0 0}
.yang-mourning-footer a{color:inherit;opacity:.75;text-decoration:none;border-bottom:1px dotted currentColor;font-size:13px;transition:opacity .18s ease}
.yang-mourning-footer a:hover{opacity:1}
</style>
<?php
echo ob_get_clean();
} }
add_action('wp_footer', 'yang_mourning_footer_link', 20);
/** /**
* 当前请求是不是说明页自己 * 当前请求是不是说明页自己
+1 -1
View File
@@ -1,7 +1,7 @@
/* /*
Theme Name: Kratos_Yang Theme Name: Kratos_Yang
Text Domain: kratos_Yang Text Domain: kratos_Yang
Version: 3.2.13 Version: 3.2.14
Requires PHP: 7.0 Requires PHP: 7.0
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。 Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好 Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好