diff --git a/inc/theme-mail-template.php b/inc/theme-mail-template.php index dcf6c1c..a786b79 100644 --- a/inc/theme-mail-template.php +++ b/inc/theme-mail-template.php @@ -69,21 +69,343 @@ function yang_mail_shade($hex, $ratio = 0.82) * * 用相对亮度公式(WCAG),不是简单取平均 —— 人眼对绿最敏感、蓝最不敏感, * 直接平均会把 #00ff00 判成中等亮度。 + * + * ── 阈值为什么是 0.179 而不是 0.5 ──────────────────────────── + * 「白字对比度 = 黑字对比度」的那个点,可以解出来: + * 白字对比度 = 1.05 / (L + 0.05) + * 黑字对比度 = (L + 0.05) / 0.05 + * 两者相等 ⇒ (L + 0.05)² = 0.0525 ⇒ L ≈ 0.179 + * 所以在 L = 0.179 处,白字与黑字一样清楚;低于它白字更好,高于它黑字更好。 + * 之前写 0.5,把一整段"其实该用深色字"的中间亮度全划成了"深色", + * 典型受害者就是亮蓝 #00a2ff(L=0.331):被判成深色 ⇒ 压白字 ⇒ 只有 2.76:1, + * 远低于 AA 的 4.5,品牌条上的站名其实很难看清。按 0.179 判定后走深色字,6.31:1。 + * + * ⚠️ 这个函数只回答"深还是浅",**不负责保证对比度**。中灰 #808080 落在 + * 交叉点附近,白字 3.95、深字 4.41 —— 两个都不到 4.5,但必须选一个。 + * 所以配字时不要直接用它,用 yang_mail_fg_on():那里是"黑白里取更优", + * 顺带在都不达标时退回纯黑/纯白,能多榨出一两档。 */ function yang_mail_is_dark($hex) +{ + return yang_mail_lum($hex) < 0.179; +} + +/** + * 相对亮度(WCAG,0=纯黑 1=纯白) + */ +function yang_mail_lum($hex) { $hex = ltrim((string) $hex, '#'); - if (strlen($hex) !== 6) { - return true; + if (!preg_match('/^[0-9a-fA-F]{6}$/', $hex)) { + return 0.0; + } + $lin = function ($c) { + $c /= 255; + return $c <= 0.03928 ? $c / 12.92 : pow(($c + 0.055) / 1.055, 2.4); + }; + return 0.2126 * $lin(hexdec(substr($hex, 0, 2))) + + 0.7152 * $lin(hexdec(substr($hex, 2, 2))) + + 0.0722 * $lin(hexdec(substr($hex, 4, 2))); +} + +/** + * 给某个底色配文字色:在白与深之间取**对比度更高**的那个 + * + * 为什么不是"深色就配白、浅色就配深":中灰区(如 #808080)两边都不达标 + * (白字 3.95、深字 4.41),二元判定的答案取决于阈值落在哪一侧, + * 而阈值本身对两个都不达标的情况毫无意义。取更优的那个至少是当前最好的选择, + * 而且这个判据跟"阈值该定多少"完全解耦 —— 换阈值也不会选错。 + */ +function yang_mail_fg_on($bg) +{ + $white = yang_mail_contrast('#ffffff', $bg); + $dark = yang_mail_contrast('#1a1a1a', $bg); + if ($white >= $dark) { + return '#ffffff'; + } + // 深字够不着 AA 时,纯黑还能再挤一点(#808080 上 4.41 → 5.32) + return yang_mail_contrast('#000000', $bg) > $dark ? '#000000' : '#1a1a1a'; +} + +/** + * 取一个颜色的「色度」(chroma)—— 三个通道的最大差 + * + * ── 为什么不用 HSL 饱和度判断"有没有颜色" ──────────────────── + * HSL 的 S 在高亮度区会虚高。实测派生底 #f0f9ff(几乎纯白带一点点蓝) + * 的 HSL 饱和度是 **1.00**,而 #e5e5e6(纯灰)是 0.005 —— 看起来判据很灵。 + * 但 #e9eaec 这种"肉眼就是灰"的颜色,HSL 饱和度也有 0.12, + * 会被判成"有颜色"。真正决定人眼能否看出颜色的,是通道极差(色度): + * #f0f9ff → 0.059(看得出是极浅蓝) + * #e9eaec → 0.012(看不出,就是灰) + * 两者 HSL 饱和度一个是 1.00、一个 0.12,顺序跟人的感受**正好相反**。 + * 所以这里一律用色度,不用 S。 + */ +function yang_mail_chroma($hex) +{ + $hex = ltrim((string) $hex, '#'); + if (!preg_match('/^[0-9a-fA-F]{6}$/', $hex)) { + return 0.0; + } + $v = array( + hexdec(substr($hex, 0, 2)), + hexdec(substr($hex, 2, 2)), + hexdec(substr($hex, 4, 2)), + ); + return (max($v) - min($v)) / 255; +} + +/** + * 把「有色相但太淡」的颜色沿**它自己的色相**加饱和,直到色度达到下限 + * + * ── 为什么需要这一步 ──────────────────────────────────────── + * 主题默认主色是 #24292e,色度只有 0.039(几乎是中性深灰)。 + * 往白里兑出浅底后色度只剩 0.002 —— 物理上不可见,整封邮件仍旧是黑白的, + * 用户看到的还是「模板没做好」。但「从灰里调出彩色」在数学上不可能, + * 而主题默认值就是它,所以必须有一个补饱和的环节。 + * + * 只动饱和度,**色相与亮度一律不动** —— 派生色仍属同一色系,品牌识别没丢。 + */ +function yang_mail_ensure_chroma($hex, $minChroma) +{ + if (yang_mail_chroma($hex) >= $minChroma) { + return $hex; + } + + list($hue, $l) = yang_mail_hsl($hex); + $den = 1 - abs(2 * $l - 1); + if ($den < 1e-6) { + // 纯白或纯黑:没有色相可言 + return $hex; + } + $s0 = yang_mail_chroma($hex) / $den; + + /* 逐档搜索而不是解析求解:色度是 max-min,跟 S 的关系在 l>0.5 与 + * l<0.5 两侧公式不同,硬推容易在边界写错;这里步数只有几十。 */ + for ($s = $s0; $s <= 0.95; $s += 0.02) { + $out = yang_mail_hsl_hex($hue, $s, $l); + if (yang_mail_chroma($out) >= $minChroma) { + return $out; + } + } + return yang_mail_hsl_hex($hue, 0.95, $l); +} + +/** + * 这个颜色有没有「真实色相」(可以放心拿它的色相去染色) + * + * ── 为什么判据放在源头,而不是放在每个派生色上 ────────────────── + * 与白色混合是**同比压缩**色度:0.94 那一档压 16 倍。 + * 所以"派生出的浅底色度很低"是**必然现象**,不是"这个颜色没有色相"。 + * 早先的实现在 yang_mail_ensure_chroma() 里对入参做 0.02 死区判断, + * 于是极浅底(混合后 0.008)被当成"没有色相"直接跳过, + * 兜底等于没做 —— 恰恰是最需要兜底的那一档失效了。 + * + * 真正该判断的是**种子**:种子的色度 ≥ 0.02 说明它有一个真实可辨的色相 + * (#24292e 是 0.039,属"有一点点真实的蓝",可以放大); + * 低于 0.02 的(#808080 这种纯中性灰)色相是浮点噪声,按噪声染色 + * 等于随机配色,还会让「主色本来就没有色相」变得不可观测 —— 那就不染。 + */ +function yang_mail_has_hue($hex) +{ + return yang_mail_chroma($hex) >= 0.02; +} + +/** + * #rrggbb → array(色相 0~1, 亮度 0~1) + */ +function yang_mail_hsl($hex) +{ + $hex = ltrim((string) $hex, '#'); + if (!preg_match('/^[0-9a-fA-F]{6}$/', $hex)) { + return array(0.0, 0.0); } $r = hexdec(substr($hex, 0, 2)) / 255; $g = hexdec(substr($hex, 2, 2)) / 255; $b = hexdec(substr($hex, 4, 2)) / 255; - $lin = function ($c) { - return $c <= 0.03928 ? $c / 12.92 : pow(($c + 0.055) / 1.055, 2.4); + $mx = max($r, $g, $b); + $mn = min($r, $g, $b); + $l = ($mx + $mn) / 2; + $d = $mx - $mn; + if ($d < 1e-9) { + return array(0.0, $l); + } + if ($mx === $r) { + $h = (($g - $b) / $d + ($g < $b ? 6 : 0)) / 6; + } elseif ($mx === $g) { + $h = (($b - $r) / $d + 2) / 6; + } else { + $h = (($r - $g) / $d + 4) / 6; + } + return array($h, $l); +} + +/** + * HSL → #rrggbb(h 为 0~1) + */ +function yang_mail_hsl_hex($h, $s, $l) +{ + $hue2rgb = function ($p, $q, $t) { + if ($t < 0) { + $t += 1; + } + if ($t > 1) { + $t -= 1; + } + if ($t < 1 / 6) { + return $p + ($q - $p) * 6 * $t; + } + if ($t < 1 / 2) { + return $q; + } + if ($t < 2 / 3) { + return $p + ($q - $p) * (2 / 3 - $t) * 6; + } + return $p; }; - $L = 0.2126 * $lin($r) + 0.7152 * $lin($g) + 0.0722 * $lin($b); - return $L < 0.5; + if ($s <= 0) { + $v = (int) round($l * 255); + return sprintf('#%02x%02x%02x', $v, $v, $v); + } + $q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s; + $p = 2 * $l - $q; + return sprintf( + '#%02x%02x%02x', + (int) round(255 * $hue2rgb($p, $q, $h + 1 / 3)), + (int) round(255 * $hue2rgb($p, $q, $h)), + (int) round(255 * $hue2rgb($p, $q, $h - 1 / 3)) + ); +} + +/** + * 生成整封邮件用的配色表(全部从主色派生,同色系) + * + * ── 为什么整封邮件不能是灰的 ────────────────────────────────── + * 早先模板里只有品牌条、徽标、按钮、最后一块引用块的竖线用主色, + * 其余 90% 面积(标题、正文、引用块底色、信息表分隔线、页脚)全是中性灰 + * (#1a2028 / #5f6b78 / #c2cbd4 / #edf0f3)。主色若本身不够艳 + * (比如主题默认的 #24292e 是接近中性的深灰蓝),整封信看起来就是黑白的。 + * 「彩色」不是靠一个大色块,而是让**底色、描边、强调文字**都在同一色系里。 + * + * ── 为什么要算而不是写死 ────────────────────────────────────── + * 主色是用户在主题选项里改的,写死一套蓝就没有意义了。 + * 这里从主色派生出:极浅底(区块背景)、浅底(页脚背景)、 + * 中档描边、深档文字(保证对浅底 ≥4.5:1)。 + * + * ── 派生为什么不能只是「与白混合」──────────────────────────── + * 混合是同比压缩色度:0.94 那一档把色度压 16 倍。主色是 #24292e 这种 + * 近中性深灰(色度 0.039)时,兑完只剩 0.002,肉眼与纯灰无异 —— + * 这正是「换成默认主色后邮件又变黑白」的机制。 + * 所以浅底在混合之后还要过一道 yang_mail_ensure_chroma(),按**它自己所在 + * 亮度**补一个可见的色度下限(浅底用 MIN_CHROMA_TINT,中档用 MIN_CHROMA_MID)。 + * 这一步只动饱和度、不动色相和亮度,颜色仍是主色的同一色系。 + * + * ── 品牌条为什么仍用原色 ──────────────────────────────────── + * 用户直接看到主色的地方(品牌条、按钮、徽标)一律用他填的原色。 + * 他选了深灰蓝,就不该在标题栏上看到它被换成亮蓝。补饱和只用于 + * 「主色的稀释/加深」,那是他自己没指定过的颜色。 + * + * 全部输出**实色**,不用 rgba:Outlook(Word 引擎)不认 rgba,会整条丢掉背景。 + */ +function yang_mail_palette($brand = null) +{ + if ($brand === null) { + $brand = yang_mail_brand_color(); + } + $brand = ltrim((string) $brand, '#'); + if (!preg_match('/^[0-9a-fA-F]{6}$/', $brand)) { + $brand = '24292e'; + } + $brand = '#' . $brand; + + /* 派生用的种子:中性一点的先提饱和,否则这一整套派生色都将是灰的。 + * 只在主色有**真实色相**时才放大(见 yang_mail_has_hue); + * 零色相的纯灰宁可保持灰,也不要凭空编一个色相。 */ + $seed = yang_mail_has_hue($brand) ? yang_mail_ensure_chroma($brand, 0.30) : $brand; + + $onBrand = yang_mail_fg_on($brand); + + /* 浅底:混合后按自身亮度再补一次色度。 + * 极浅底(ratio 0.94)本身亮度很高,能容纳的色度有限, + * 所以下限分两档 —— 用同一个值会让极浅底被迫变深、不再是"浅底"。 + * 主色零色相时不补:那套灰是事实,硬染就是编造。 */ + $keep = yang_mail_has_hue($brand); + $tintXX = $keep ? yang_mail_ensure_chroma(yang_mail_tint($seed, 0.94), 0.045) + : yang_mail_tint($seed, 0.94); + $tintX = $keep ? yang_mail_ensure_chroma(yang_mail_tint($seed, 0.88), 0.075) + : yang_mail_tint($seed, 0.88); + $tintLine = $keep ? yang_mail_ensure_chroma(yang_mail_tint($seed, 0.80), 0.110) + : yang_mail_tint($seed, 0.80); + $tintMid = $keep ? yang_mail_ensure_chroma(yang_mail_tint($seed, 0.55), 0.220) + : yang_mail_tint($seed, 0.55); + $pageBg = $keep ? yang_mail_ensure_chroma(yang_mail_tint($seed, 0.90), 0.065) + : yang_mail_tint($seed, 0.90); + + /* 强调文字色:从种子一路压暗,取第一个"在白底和极浅底上都 ≥4.5"的档位。 + * 不能直接拿主色当文字色 —— 亮蓝 #00a2ff 在白底只有 2.76:1。 + * 也不能无脑取很深,那样就看不出是"主色的深版"了,所以要取**最亮的达标档**。 */ + $textOnTint = $seed; + for ($s = 0.20; $s <= 0.90; $s += 0.02) { + $c = yang_mail_shade($seed, $s); + if (yang_mail_contrast($c, '#ffffff') >= 4.5 + && yang_mail_contrast($c, $tintXX) >= 4.5) { + $textOnTint = $c; + break; + } + } + + /* 次要文字色:原来是固定的 #5c6b7a,但页脚底色改成了主色浅底之后, + * 这个固定值在浅蓝上只有 4.26:1 —— 12px 小字不达 AA。 + * 「文字色写死、背景色随主色变」这个组合本身就是错的:背景一变浅, + * 固定灰就压不住。所以改成从基准灰往下压,压到在**它实际会遇到的 + * 两种背景**(页脚浅底、邮件最外层)上都达标为止。 */ + $muted = '#5c6b7a'; + for ($t = 1.0; $t >= 0.55; $t -= 0.02) { + $cand = $t >= 0.999 ? $muted : yang_mail_shade($muted, $t); + if (yang_mail_contrast($cand, $tintX) >= 4.5 + && yang_mail_contrast($cand, $pageBg) >= 4.5) { + $muted = $cand; + break; + } + } + + return array( + 'brand' => $brand, // 主色(品牌条、按钮) + 'brand_dark' => yang_mail_shade($brand, 0.78), // 品牌条下沿深色带 + 'on_brand' => $onBrand, // 压在品牌色上的字 + 'text' => $textOnTint, // 同色系的强调文字(标题/标签) + 'tint_xx' => $tintXX, // 极浅底:引用块、信息表 + 'tint_x' => $tintX, // 稍深一点的浅底:页脚 + 'tint_line' => $tintLine, // 浅底上的描边/分隔线 + 'tint_mid' => $tintMid, // 中档:弱化竖线、次要描边 + 'page_bg' => $pageBg, // 邮件最外层背景 + 'body' => '#243040', // 正文(保持深灰蓝,长文好读) + 'muted' => $muted, // 次要文字(时间、页脚),按背景调过 + ); +} + +/** + * 计算两色的 WCAG 对比度 + * + * 给配色表自检用:派生颜色是否达标,靠算而不是靠眼睛看。 + */ +function yang_mail_contrast($fg, $bg) +{ + $lumOf = function ($hex) { + $hex = ltrim((string) $hex, '#'); + if (strlen($hex) !== 6) { + return 0; + } + $lin = function ($c) { + $c /= 255; + return $c <= 0.03928 ? $c / 12.92 : pow(($c + 0.055) / 1.055, 2.4); + }; + return 0.2126 * $lin(hexdec(substr($hex, 0, 2))) + + 0.7152 * $lin(hexdec(substr($hex, 2, 2))) + + 0.0722 * $lin(hexdec(substr($hex, 4, 2))); + }; + $a = $lumOf($fg); + $b = $lumOf($bg); + return (max($a, $b) + 0.05) / (min($a, $b) + 0.05); } /** @@ -218,9 +540,21 @@ function yang_mail_render($args) $quoteList = array($d['quote']); } - $brand = yang_mail_brand_color(); - $brandD = yang_mail_shade($brand, 0.78); - $onBrand = yang_mail_is_dark($brand) ? '#ffffff' : '#1a1a1a'; + /* 整套配色从主色派生一次,后面所有颜色都从这里取。 + * 不在这里散落写死 #xxxxxx —— 那样主色一改,「彩色」就断了, + * 又退回成"一个色块 + 一堆灰」。 */ + $pal = yang_mail_palette(); + $brand = $pal['brand']; + $brandD = $pal['brand_dark']; + $onBrand = $pal['on_brand']; + $cText = $pal['text']; + $cBody = $pal['body']; + $cMuted = $pal['muted']; + $cTintXX = $pal['tint_xx']; + $cTintX = $pal['tint_x']; + $cTintLine = $pal['tint_line']; + $cTintMid = $pal['tint_mid']; + $cPageBg = $pal['page_bg']; // badge 的背景:把主色往白色方向提亮一档,得到比主色浅、但同色相的实色。 // 主色本身是深色时提亮 22%,浅色时压暗 —— 否则浅主色上叠浅 badge 会看不见。 @@ -256,22 +590,22 @@ function yang_mail_render($args) - + -
+
- +
- +
@@ -309,13 +643,13 @@ function yang_mail_render($args)
-
+
-
+
@@ -330,23 +664,23 @@ function yang_mail_render($args) * 只有一个块时它既是第一也是最后 ⇒ 拿到主色,与旧版行为一致。 */ $isLast = ($qi === count($quoteList) - 1); - $barColor = $isLast ? $brand : '#c2cbd4'; - $barBg = $isLast ? '#f6f8fa' : '#fafbfc'; + $barColor = $isLast ? $brand : $cTintMid; + $barBg = $isLast ? $cTintXX : $cTintX; $barMargin = $isLast ? '22px' : '14px'; ?>
-
+
-
+
-
+
@@ -358,13 +692,13 @@ function yang_mail_render($args) - +
- - - @@ -410,7 +744,7 @@ function yang_mail_render($args)
+ + -
+
@@ -396,13 +730,13 @@ function yang_mail_render($args)
-
+
+
- +
此邮件由系统自动发出,请勿直接回复。
-
©
+
©
-
+
您收到这封邮件,是因为本站记录了与您邮箱相关的订阅或互动。
diff --git a/style.css b/style.css index d2f0845..232f68d 100644 --- a/style.css +++ b/style.css @@ -1,7 +1,7 @@ /* Theme Name: Kratos_Yang Text Domain: kratos_Yang -Version: 3.2.7 +Version: 3.2.8 Requires PHP: 7.0 Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。 Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好