diff --git a/assets/css/frontend/base.css b/assets/css/frontend/base.css index 85a96051..6327b8f3 100644 --- a/assets/css/frontend/base.css +++ b/assets/css/frontend/base.css @@ -162,6 +162,53 @@ main.container, } } +/* ========== User identity inline icons ========== + * Level / medal icons ride inline to the right of a username. Centralized here + * so every route (header panel, user center, author hero, single, comments, + * widgets) renders the same compact badge instead of the raw intrinsic PNG size. + * + * Mirrors zibll `.img-icon { height: 1.1em; display: inline-block; vertical-align: -.15em }` + * — sizes to the surrounding line height, never dominates the row. Localized + * exceptions (UC level hero, admin column) override `height` per context. + */ +.aksrt-user-level-icon, +.aksrt-identity-medal { + display: inline-block; + width: auto; + height: 1.1em; + max-width: 6em; + object-fit: contain; + vertical-align: -.15em; + flex: 0 0 auto; +} +/* Comment context: a touch smaller to fit the 14px author line. */ +.aksrt-identity-medal--comment, +.aksrt-user-level-icon--comment { + height: 1em; + vertical-align: -.12em; +} + +/* No-wrap inline name rows — keep the level icon on the same line as the name. + * The non-standard element (zibll compat) plus .user-name / author-hero / + * uc-header name rows must not flex-wrap, or the badge drops to a new line. + * The comment header may still wrap (avatar + name + like can overflow on + * narrow viewports), so it keeps flex-wrap but the inner never does. */ +.article-avatar name, +.article-avatar .name, +.user-name, +.aksrt-author-hero__name, +.uc-header__name, +.aksrt-header-badges { + flex-wrap: nowrap; + white-space: nowrap; +} +.aksrt-author-hero__name, +.uc-header__name { + display: inline-flex; + align-items: center; + gap: 4px; +} + /* ========== 通用空状态 ========== */ .no-results { padding: 48px 20px; diff --git a/assets/css/frontend/comments/mobile-item.css b/assets/css/frontend/comments/mobile-item.css index 9f883d60..9c8a1149 100644 --- a/assets/css/frontend/comments/mobile-item.css +++ b/assets/css/frontend/comments/mobile-item.css @@ -30,6 +30,9 @@ .comment-header .author-box { display: flex; align-items: center; gap: 8px; width: 100%; } .comment-header .author-box name, .comment-header .author-box .flex1 { flex: 1; min-width: 0; } +/* Keep name + level badge on one line; clamp the name so the badge never wraps. */ +.comment-header .author-box name { flex-wrap: nowrap; } +.comment-header .author-box .aksrt-comment__author { max-width: 14em; } .comment-header .aksrt-comment__like, .comment-header .action-comment-like { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; diff --git a/assets/css/frontend/comments/shell.css b/assets/css/frontend/comments/shell.css index 37527bc7..f8a6c0f0 100644 --- a/assets/css/frontend/comments/shell.css +++ b/assets/css/frontend/comments/shell.css @@ -754,15 +754,16 @@ body.dark-theme .comment-login-prompt__inner { .medal-icon.img-icon, .aksrt-identity-medal { display: inline-block; - width: 1.35em; - height: 1.35em; + width: auto; + height: 1.1em; + max-width: 6em; object-fit: contain; - vertical-align: middle; + vertical-align: -.15em; margin-left: 4px; } .aksrt-identity-medal--comment { - width: 1.15em; - height: 1.15em; + width: auto; + height: 1em; } .aksrt-identity-medal.is-wear { outline: 1px solid rgba(245, 158, 11, 0.45); @@ -782,3 +783,9 @@ body.dark-theme .comment-login-prompt__inner { align-items: center; gap: 4px; } +/* Inline name + level badge stay on one line; only the outer author-box wraps. */ +.aksrt-comment__header name, +.aksrt-comment__header .author-box > name { + flex-wrap: nowrap; + white-space: nowrap; +} diff --git a/assets/css/frontend/nav/header-userbox.css b/assets/css/frontend/nav/header-userbox.css index 971dcd9b..88d8bc86 100644 --- a/assets/css/frontend/nav/header-userbox.css +++ b/assets/css/frontend/nav/header-userbox.css @@ -210,6 +210,32 @@ flex-wrap: wrap; gap: 4px; } +/* Inline name + level badge in the dropdown profile row — one line, no wrap. */ +.sub-user-box .user-right > .flex1 { + min-width: 0; +} +.sub-user-box .user-right b { + display: inline-flex; + align-items: center; + gap: 4px; + flex-wrap: nowrap; + white-space: nowrap; + max-width: 100%; + overflow: hidden; +} +.sub-user-box .user-right b .display-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; + max-width: 100%; +} +.sub-user-box .user-right .aksrt-user-level-icon, +.sub-user-box .user-right .aksrt-identity-medal { + height: 1em; + vertical-align: -.12em; + flex: 0 0 auto; +} /* User dropdown light-theme text contrast */ .user-dropdown.sub-user-box, diff --git a/assets/css/frontend/single-post/author-actions.css b/assets/css/frontend/single-post/author-actions.css index 96fbb8e8..eb1b11a6 100644 --- a/assets/css/frontend/single-post/author-actions.css +++ b/assets/css/frontend/single-post/author-actions.css @@ -640,7 +640,7 @@ html.dark .post-drop-meta__item:hover { .user-card.author .user-avatar { display: flex; justify-content: center; } .user-card.author .user-avatar .avatar, .user-card.author .user-avatar img { width: 80px; height: 80px; border-radius: 50%; object-fit: cover; } -.user-card.author .user-name { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 4px; } +.user-card.author .user-name { display: flex; flex-wrap: nowrap; align-items: center; justify-content: center; gap: 4px; } .user-card.author .user-name .display-name { font-weight: 700; color: var(--key-color, inherit); } .user-card.author .user-desc { text-align: center; } .user-card.author .user-btns { text-align: center; } diff --git a/assets/css/frontend/single-post/cover.css b/assets/css/frontend/single-post/cover.css index 3af39fea..b0d983c9 100644 --- a/assets/css/frontend/single-post/cover.css +++ b/assets/css/frontend/single-post/cover.css @@ -277,7 +277,7 @@ } .article-avatar name.flex { gap: 4px; - flex-wrap: wrap; + flex-wrap: nowrap; } .article-avatar .aksrt-vip-badge, .article-avatar .aksrt-identity-chip { @@ -286,11 +286,13 @@ vertical-align: middle; } .article-avatar .medal-icon.img-icon, -.article-avatar .aksrt-identity-medal { - width: 1.4em; - height: 1.4em; +.article-avatar .aksrt-identity-medal, +.article-avatar .aksrt-user-level-icon { + width: auto; + height: 1.2em; + max-width: 6em; object-fit: contain; - vertical-align: middle; + vertical-align: -.15em; margin-left: 4px; } .article-avatar .display-name { diff --git a/assets/css/routes/profile/layout.css b/assets/css/routes/profile/layout.css index 42d9a1f7..cf4c869f 100644 --- a/assets/css/routes/profile/layout.css +++ b/assets/css/routes/profile/layout.css @@ -535,14 +535,7 @@ color: #fff; } -.aksrt-user-level-icon { - display: inline-block; - width: auto; - height: 1.15em; - max-width: 6em; - object-fit: contain; - vertical-align: -.18em; -} +/* Level icon base sizing is centralized in assets/css/frontend/base.css. */ /* 用户中心 layout 间距;双栏 / sticky 见 user-center/shell.css(body.aksrt-user-center)。 */ .aksrt-profile__layout { diff --git a/assets/css/routes/user-center/level.css b/assets/css/routes/user-center/level.css index f22b45f5..bf084cb2 100644 --- a/assets/css/routes/user-center/level.css +++ b/assets/css/routes/user-center/level.css @@ -1,12 +1,6 @@ -/* User level — dedicated center panel */ -.aksrt-user-level-icon { - display: inline-block; - width: auto; - height: 1.35em; - max-width: 6em; - object-fit: contain; - vertical-align: -.2em; -} +/* User level — dedicated center panel. + * Base level icon sizing lives in assets/css/frontend/base.css (1.1em, inline). + * This file keeps only the UC-level-specific hero/road overrides + admin column. */ .aksrt-user-level-icon--admin { height: 22px; } .uc-level-page { display: grid; gap: 16px; } diff --git a/inc/helpers/aksrt-header-userbox.php b/inc/helpers/aksrt-header-userbox.php index 137f937b..eed8feb6 100644 --- a/inc/helpers/aksrt-header-userbox.php +++ b/inc/helpers/aksrt-header-userbox.php @@ -91,8 +91,8 @@ if (!function_exists('aksrt_header_user_badges_row')) : if ($user_id <= 0 || !function_exists('aksrt_render_user_identity_chips')) { return ''; } - // VIP already shown next to display name in the header profile row. - $chips = aksrt_render_user_identity_chips($user_id, 'card', ['include_vip' => false]); + // VIP + level already shown next to display name in the header profile row. + $chips = aksrt_render_user_identity_chips($user_id, 'card', ['include_vip' => false, 'skip_level' => true]); if ($chips === '') { return ''; } diff --git a/inc/helpers/aksrt-identity.php b/inc/helpers/aksrt-identity.php index 1c9642b4..f9fc2a89 100644 --- a/inc/helpers/aksrt-identity.php +++ b/inc/helpers/aksrt-identity.php @@ -35,6 +35,7 @@ if (!function_exists('aksrt_render_user_identity_chips')) { * Optional. Additional options. * * @type bool $include_vip Whether to prepend VIP badge. Default true. + * @type bool $skip_level Whether to omit the level chip (rendered inline elsewhere). Default false. * } */ function aksrt_render_user_identity_chips(int $user_id, string $context = 'author', array $args = []): string { @@ -43,6 +44,7 @@ if (!function_exists('aksrt_render_user_identity_chips')) { } $args = wp_parse_args($args, [ 'include_vip' => true, + 'skip_level' => false, ]); $html = ''; if (!empty($args['include_vip']) && function_exists('aksrt_render_vip_badge')) { @@ -63,12 +65,16 @@ if (!function_exists('aksrt_render_user_identity_chips')) { if ($label === '') { continue; } + $type = sanitize_html_class((string) ($signal['type'] ?? '')); + // Level is rendered inline next to the name by the caller; skip it here. + if (!empty($args['skip_level']) && $type === 'level') { + continue; + } $tone = sanitize_html_class((string) ($signal['tone'] ?? 'info')); $icon = sanitize_html_class((string) ($signal['icon'] ?? 'check')); $icon_url = esc_url((string) ($signal['icon_url'] ?? '')); $title = (string) ($signal['title'] ?? $label); $is_wear = !empty($signal['wear']); - $type = sanitize_html_class((string) ($signal['type'] ?? '')); // Image medal when dictionary provides icon_url. if ($icon_url !== '') { @@ -162,7 +168,8 @@ if (!function_exists('aksrt_get_user_name')) { if ($icon_url !== '') { $level_html = '' . esc_attr($label) . ''; } else { - $level_html = ' ' . esc_html($label) . ''; + // Text fallback mirrors zibll LV badge: small pill, never oversized. + $level_html = '' . esc_html($label) . ''; } } elseif ($type === 'badge' && !empty($args['medal']) && $medal_html === '' && !empty($signal['wear'])) { if ($icon_url !== '') { diff --git a/inc/helpers/header/aksrt-header-navbar.php b/inc/helpers/header/aksrt-header-navbar.php index dbdbd637..d4fa057e 100644 --- a/inc/helpers/header/aksrt-header-navbar.php +++ b/inc/helpers/header/aksrt-header-navbar.php @@ -151,6 +151,10 @@ if (!function_exists('aksrt_header_user_box_html')) : $avatar = get_avatar($user_id, 44, '', esc_attr($display_name), ['loading' => 'lazy']); $desc = (string) ($ctx['user_desc'] ?? ''); $vip_badge = function_exists('aksrt_render_vip_badge') ? aksrt_render_vip_badge($user_id, 'sm') : ''; + // Level icon rides inline to the right of the display name (zibll parity). + $level_badge = (class_exists('\AKSRT\User\Level') && method_exists('\AKSRT\User\Level', 'get_badge_html')) + ? \AKSRT\User\Level::get_badge_html($user_id, 'aksrt-user-level-icon--header') + : ''; $payvip = function_exists('aksrt_header_payvip_bar') ? aksrt_header_payvip_bar($user_id) : ''; // nav_user_pay_vip gate: empty payvip when switch off for non-VIP CTA only — helper already handles VIP status. if (empty($ctx['nav_user_pay_vip']) && $payvip !== '' && strpos($payvip, 'is-vip') === false) { @@ -239,7 +243,7 @@ if (!function_exists('aksrt_header_user_box_html')) : $con .= $avatar; $con .= '
'; $con .= '
' - . esc_html($display_name) . $vip_badge + . '' . esc_html($display_name) . '' . $vip_badge . $level_badge . '
' . esc_html($desc) . '
'; $con .= '
'; $con .= $msg_icon; diff --git a/inc/helpers/user-center-renderers/profile-header.php b/inc/helpers/user-center-renderers/profile-header.php index a829a84a..113169ce 100644 --- a/inc/helpers/user-center-renderers/profile-header.php +++ b/inc/helpers/user-center-renderers/profile-header.php @@ -41,6 +41,10 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ if ($vip_badge) { echo $vip_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } + // Level icon inline to the right of the name (zibll parity). + if (class_exists('\AKSRT\User\Level') && method_exists('\AKSRT\User\Level', 'get_badge_html')) { + echo \AKSRT\User\Level::get_badge_html((int) $user_id); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } ?> description) : ?> @@ -49,6 +53,12 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_
+ diff --git a/templates/parts/user/author-hero.php b/templates/parts/user/author-hero.php index ff6683d8..ccd68ce6 100644 --- a/templates/parts/user/author-hero.php +++ b/templates/parts/user/author-hero.php @@ -98,6 +98,10 @@ $pm_url = ($pm_enabled && $messages_url !== '') if ($vip_badge) { echo $vip_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } + // Level icon inline to the right of the name (zibll parity). + if (class_exists('\AKSRT\User\Level') && method_exists('\AKSRT\User\Level', 'get_badge_html')) { + echo \AKSRT\User\Level::get_badge_html($user_id); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } ?> @@ -124,7 +128,10 @@ $pm_url = ($pm_enabled && $messages_url !== '') - false]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + false, 'skip_level' => true]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?>