Files
wordpress-theme-aksrt/src/Frontend/InlineStyleBuilder.php
T

211 lines
11 KiB
PHP
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.
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Frontend;
defined('ABSPATH') || exit;
class InlineStyleBuilder {
public function output(): void {
$logo_url = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::logo_url() : (string) aksrt_get_setting('logo_url', '');
$dark_logo_url = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::logo_dark_url() : (string) aksrt_get_setting('site_logo_dark', '');
$favicon = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::favicon_url() : (string) aksrt_get_setting('favicon_url', '');
$primary_color = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::theme_color() : '#f04494';
$dark_primary = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::dark_theme_color() : '';
$main_radius = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::main_radius() : 12;
$container_max = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::container_max_width() : 1200;
$font_family = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::font_family() : 'system';
$logo_width = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::logo_width() : 150;
$sidebar_cfg = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_sidebar_config() : ['width' => 311, 'enabled' => true, 'position' => 'right'];
$list_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings() : [];
$content_width = $container_max . 'px';
$container_wide = max(1280, $container_max + 160) . 'px';
$font_custom = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::font_family_custom() : '';
$font_stacks = $this->resolve_font_stacks($font_family, $font_custom);
// legacy hard-codes 311px dual-column math; keep runtime width aligned to that
// baseline when settings still hold the old 300 default (or empty).
$sidebar_width = (int) ($sidebar_cfg['width'] ?? 311);
if ($sidebar_width <= 0 || $sidebar_width === 300) {
$sidebar_width = 311;
}
$sidebar_width = max(200, min(400, $sidebar_width));
$layout_vars = [
'--container' => $content_width,
'--container-wide' => $container_wide,
'--mian-max-width' => $content_width, // legacy typo token
'--sidebar-width' => $sidebar_width . 'px',
'--radius-card' => $main_radius . 'px',
'--shadow-md' => '0 8px 24px rgba(20, 28, 48, 0.10)',
'--main-shadow' => 'rgba(116, 116, 116, 0.08)',
'--font-body' => $font_stacks['body'],
'--font-display' => $font_stacks['display'],
'--font-ui' => $font_stacks['ui'],
'--font-heading' => $font_stacks['ui'],
'--aksrt-logo-width' => $logo_width . 'px',
'--nav-height' => '60px',
];
echo '<style id="aksrt-runtime-vars">:root{';
foreach ($layout_vars as $name => $value) {
// esc_attr() would turn font-stack quotes into literal &quot; inside <style>,
// invalidating font-family; strip only characters that can break out of a declaration.
echo esc_html($name) . ':' . $this->css_value((string) $value) . ';';
}
if ($logo_url) {
echo '--aksrt-logo-url:"' . esc_url($logo_url) . '";';
}
if ($dark_logo_url) {
echo '--aksrt-logo-dark-url:"' . esc_url($dark_logo_url) . '";';
}
if ($favicon) {
echo '--aksrt-favicon:"' . esc_url($favicon) . '";';
}
echo '}</style>' . "\n";
// 对齐参考主题 zib_head_css():主题色变量与 --main-radius 挂在 body(main.css 的定义层级),
// 夜间主题色仅在单独设置时覆盖 body.dark-theme。
// main.css declares these defaults on body; :root values cannot override them by inheritance.
echo '<style id="aksrt-theme-color">body{' . $this->theme_color_vars($primary_color)
. '--main-radius:' . (int) $main_radius . 'px;'
. '--posts-list-scale:' . (int) ($list_settings['list_list_option']['scale'] ?? 70) . '%;'
. '--posts-card-scale:' . (int) ($list_settings['list_card_option']['scale'] ?? 70) . '%;}';
if ($dark_primary !== '') {
echo 'html.dark body,body.dark-theme{' . $this->theme_color_vars($dark_primary) . '}';
}
if (class_exists('\AKSRT\Settings\Settings')) {
$bg_css = \AKSRT\Settings\Settings::background_css('theme_img_bg');
$dark_bg_css = \AKSRT\Settings\Settings::background_css('dark_theme_img_bg');
if ($bg_css !== '') {
echo 'body{' . $bg_css . '}';
}
if ($dark_bg_css !== '') {
echo 'html.dark body,body.dark-theme{' . $dark_bg_css . '}';
}
}
echo '</style>' . "\n";
if (class_exists('\AKSRT\Settings\Settings')) {
$mobile_meta = sanitize_key((string) ($list_settings['list_meta_show'] ?? 'like'));
if (!in_array($mobile_meta, ['view', 'like', 'comm'], true)) {
$mobile_meta = 'like';
}
echo '<style id="aksrt-post-list-mobile-meta">@media (max-width:640px){.meta-right .meta-' . esc_attr($mobile_meta) . '{display:unset!important}}</style>' . "\n";
}
// legacy header_theme_custom → --header-bg / --header-color on .header.
if (class_exists('\AKSRT\Settings\Settings')) {
$ht = \AKSRT\Settings\Settings::get_header_theme_config();
if (!empty($ht['custom'])) {
$h_bg = $ht['bg_custom'] !== '' ? $ht['bg_custom'] : ($ht['bg'] ?? '');
$h_fg = $ht['color_custom'] ?? '';
if ($h_bg !== '' || $h_fg !== '') {
echo '<style id="aksrt-header-theme">.header{';
if ($h_bg !== '') {
echo '--header-bg:' . esc_attr($h_bg) . ';background:' . esc_attr($h_bg) . ';';
}
if ($h_fg !== '') {
echo '--header-color:' . esc_attr($h_fg) . ';color:' . esc_attr($h_fg) . ';';
}
echo '}</style>' . "\n";
}
}
}
echo '<style id="aksrt-runtime-components">'
. '.aksrt-search-hot-keywords{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:12px;font-family:var(--font-ui);}'
. '.aksrt-search-hot-keywords__label{font-size:12px;color:var(--color-ink-3);}'
. '.aksrt-search-hot-keywords__list{display:flex;gap:8px;flex-wrap:wrap;}'
. '.aksrt-search-hot-keywords__item{display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;background:var(--surface-2,#f3f4f6);color:var(--color-ink-2,#374151);font-size:12px;line-height:1.4;text-decoration:none;}'
. '.aksrt-search-hot-keywords__item:hover{background:var(--color-primary,#2563eb);color:#fff;}'
. '.site-footer__copyright--extra{margin-top:8px;opacity:.78;}'
. '</style>' . "\n";
}
private function resolve_font_stacks(string $family, string $custom = ''): array {
// Same system stack as tokens.css; "system" must not swap in a serif face.
$system = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif';
switch ($family) {
case 'noto-sans-sc':
$stack = '"Noto Sans SC", "Source Han Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif';
break;
case 'noto-serif-sc':
$stack = '"Noto Serif SC", "Source Han Serif SC", "Songti SC", "STSong", SimSun, serif';
break;
case 'custom':
$stack = $custom !== '' ? $custom . ', ' . $system : $system;
break;
default:
$stack = $system;
}
return ['body' => $stack, 'display' => $stack, 'ui' => $stack];
}
/** 参考主题色变量族(--theme-color / --focus-* 透明度)+ AKSRT --color-primary* 族;$hex 已由 Settings 校验。 */
private function theme_color_vars(string $hex): string {
$rgb = $this->hex_to_rgb($hex);
$rgba = static function (float $alpha) use ($rgb): string {
return sprintf('rgba(%d,%d,%d,%s)', $rgb[0], $rgb[1], $rgb[2], $alpha);
};
$vars = [
'--theme-color' => $hex,
'--focus-color' => $hex,
'--focus-shadow-color' => $rgba(0.4),
'--focus-color-opacity1' => $rgba(0.1),
'--focus-color-opacity05' => $rgba(0.05),
'--focus-color-opacity3' => $rgba(0.3),
'--focus-color-opacity6' => $rgba(0.6),
'--color-primary' => $hex,
'--color-primary-hover' => $this->adjust_hex_color($hex, -0.12),
'--color-primary-active' => $this->adjust_hex_color($hex, -0.22),
'--color-primary-soft' => $rgba(0.12),
'--color-primary-softer' => $rgba(0.06),
'--color-primary-rgb' => implode(', ', $rgb),
];
$css = '';
foreach ($vars as $name => $value) {
$css .= $name . ':' . $this->css_value($value) . ';';
}
return $css;
}
/** CSS-context escape for custom property values inside a <style> block. */
private function css_value(string $value): string {
return trim((string) preg_replace('/[<>{};\\\\]/', '', $value));
}
private function hex_to_rgb(string $hex): array {
$hex = ltrim($hex, '#');
if (strlen($hex) === 3) {
$hex = preg_replace('/(.)/', '$1$1', $hex);
}
if (!is_string($hex) || strlen($hex) !== 6) {
return [136, 171, 218];
}
return [
hexdec(substr($hex, 0, 2)),
hexdec(substr($hex, 2, 2)),
hexdec(substr($hex, 4, 2)),
];
}
private function adjust_hex_color(string $hex, float $amount): string {
$rgb = $this->hex_to_rgb($hex);
$adjusted = array_map(
static function (int $channel) use ($amount): int {
$base = $amount >= 0
? $channel + ((255 - $channel) * $amount)
: $channel * (1 + $amount);
return max(0, min(255, (int) round($base)));
},
$rgb
);
return sprintf('#%02X%02X%02X', $adjusted[0], $adjusted[1], $adjusted[2]);
}
}