design(vip): refresh membership card experience

Introduce state-aware membership hierarchy, token-based styling, responsive level cards, and a non-interactive permanent VIP2 state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 19:28:57 +08:00
co-authored by Claude Fable 5
parent 0551260403
commit b60894a371
2 changed files with 403 additions and 97 deletions
+340 -52
View File
@@ -1,41 +1,10 @@
/**
* User Center — compact membership summary.
* User Center — VIP page UX/UI.
*
* The VIP tab keeps one thin status surface; personal profile chrome and
* decorative stacked-card layers belong to the user-center header, not here.
* The summary card is the membership status thesis: one calm surface,
* one state accent, and a quiet crown watermark behind the four key lines.
*/
.uc-vip-summary {
position: relative;
min-width: 0;
margin: 0 0 16px;
padding: 12px 16px;
overflow: hidden;
border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.08));
border-radius: var(--main-radius, var(--radius, 8px));
background: var(--surface-1, #fff);
box-shadow: none;
isolation: isolate;
}
.uc-vip-summary__watermark {
position: absolute;
top: 50%;
right: -10px;
z-index: 0;
color: currentColor;
font-size: 160px;
line-height: 1;
opacity: 0.15;
pointer-events: none;
transform: translateY(-50%);
}
.uc-vip-summary__watermark i {
font-size: inherit;
line-height: 1;
}
.uc-content #panel-vip.dashboard-panel {
margin: 0;
padding: 0;
@@ -45,64 +14,383 @@
box-shadow: none;
}
.uc-vip-summary {
--vip-state: var(--color-primary);
--vip-state-soft: var(--color-primary-soft);
--vip-level: var(--color-ink-2);
position: relative;
isolation: isolate;
min-width: 0;
margin: 0 0 var(--space-5, 20px);
padding: clamp(18px, 2.8vw, 28px) clamp(18px, 3vw, 30px);
padding-inline-end: clamp(110px, 19vw, 180px);
overflow: hidden;
border: 1px solid var(--color-line-soft);
border-inline-start: 3px solid var(--vip-state);
border-radius: var(--radius-lg, var(--radius, 8px));
background-color: var(--surface-1);
background-image: linear-gradient(135deg, var(--vip-state-soft), transparent 52%);
box-shadow: var(--shadow-sm);
transition: border-color var(--dur, 0.25s) var(--ease, ease), box-shadow var(--dur, 0.25s) var(--ease, ease);
}
.uc-vip-summary[data-vip-state="active"] {
--vip-state: var(--color-success);
--vip-state-soft: color-mix(in srgb, var(--color-success) 12%, transparent);
}
.uc-vip-summary[data-vip-state="permanent"] {
--vip-state: var(--color-accent);
--vip-state-soft: var(--color-accent-soft);
}
.uc-vip-summary[data-vip-state="expired"] {
--vip-state: var(--color-error);
--vip-state-soft: color-mix(in srgb, var(--color-error) 10%, transparent);
}
.uc-vip-summary[data-vip-state="unopened"] {
--vip-state: var(--color-info);
--vip-state-soft: color-mix(in srgb, var(--color-info) 10%, transparent);
}
.uc-vip-summary[data-vip-level="1"] {
--vip-level: var(--aksrt-vip-gold-2, var(--color-warning));
}
.uc-vip-summary[data-vip-level="2"] {
--vip-level: var(--aksrt-indigo, var(--color-info));
}
.uc-vip-summary__watermark {
position: absolute;
inset-inline-end: -0.08em;
top: 50%;
z-index: 0;
color: var(--vip-state);
font-size: clamp(96px, 10vw, 160px);
line-height: 1;
opacity: 0.1;
pointer-events: none;
transform: translateY(-50%);
}
.uc-vip-summary__watermark i {
display: block;
font-size: inherit;
line-height: 1;
}
.uc-vip-status {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
gap: var(--space-2, 8px);
min-width: 0;
max-width: 48rem;
text-align: left;
}
.uc-vip-status__heading {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-2, 8px);
min-width: 0;
}
.uc-vip-status__label {
color: var(--color-ink, var(--main-color, #172033));
font-size: 14px;
margin: 0;
color: var(--color-ink);
font-size: var(--text-lg, 1.25rem);
font-weight: 750;
letter-spacing: -0.02em;
line-height: var(--leading-tight, 1.2);
}
.uc-vip-status__state {
display: inline-flex;
align-items: center;
min-height: var(--aksrt-badge-h, 22px);
padding: 3px 9px;
border: 1px solid color-mix(in srgb, var(--vip-state) 24%, transparent);
border-radius: var(--radius-pill, 100px);
background: var(--vip-state-soft);
color: var(--vip-state);
font-size: var(--text-xs, 0.75rem);
font-weight: 700;
line-height: 1.4;
line-height: 1;
white-space: nowrap;
}
.uc-vip-status__main {
display: inline-flex;
align-items: center;
gap: 7px;
gap: var(--space-2, 8px);
min-width: 0;
font-size: 1.15em;
line-height: 1.3;
color: var(--vip-level);
font-size: var(--text-lg, 1.25rem);
line-height: var(--leading-tight, 1.2);
}
.uc-vip-status__main > i {
color: var(--focus-color, var(--color-primary, #f04494));
font-size: 0.95em;
.uc-vip-status__icon {
display: inline-grid;
place-items: center;
width: 34px;
height: 34px;
flex: 0 0 auto;
border: 1px solid color-mix(in srgb, var(--vip-level) 32%, transparent);
border-radius: var(--radius-xl, 12px);
background: color-mix(in srgb, var(--vip-level) 12%, transparent);
color: var(--vip-level);
font-size: 15px;
}
.uc-vip-status__level {
min-width: 0;
overflow-wrap: anywhere;
font-weight: 750;
}
.uc-vip-status__desc,
.uc-vip-status__expiry {
min-width: 0;
font-size: 0.9em;
line-height: 1.4;
overflow-wrap: anywhere;
font-size: var(--text-sm, 0.875rem);
line-height: var(--leading-snug, 1.4);
}
.uc-vip-status__desc {
margin-top: 1px;
color: var(--color-ink-3);
}
.uc-vip-status__expiry {
margin-top: 1px;
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--vip-state);
font-weight: 600;
}
.uc-vip-status__expiry i {
font-size: 0.9em;
}
/* Level purchase cards: one spacing system, no .zib-widget margin stacking. */
.uc-vip-level-cards {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-4, 16px);
align-items: stretch;
margin: 0 0 var(--space-5, 20px);
}
.uc-vip-tab .uc-vip-level-card.zib-widget,
.uc-vip-tab .uc-vip-level-card.pay-vip-info {
position: relative;
display: block;
min-width: 0;
height: 100%;
margin: 0;
padding: 22px 20px 20px;
overflow: hidden;
border: 1px solid var(--color-line-soft);
border-top: 3px solid var(--vip-card-accent, var(--color-primary));
border-radius: var(--radius-lg, var(--radius, 8px));
background-color: var(--surface-1) !important;
background-image: linear-gradient(145deg, var(--vip-card-soft), transparent 68%) !important;
box-shadow: var(--shadow-sm);
transition: transform var(--dur-fast, 0.15s) var(--ease, ease), box-shadow var(--dur-fast, 0.15s) var(--ease, ease), border-color var(--dur-fast, 0.15s) var(--ease, ease);
}
.uc-vip-tab .uc-vip-level-card.level-1 {
--vip-card-accent: var(--aksrt-vip-gold-2, var(--color-warning));
--vip-card-soft: color-mix(in srgb, var(--aksrt-vip-gold-1, var(--color-warning)) 15%, transparent);
}
.uc-vip-tab .uc-vip-level-card.level-2 {
--vip-card-accent: var(--aksrt-indigo, var(--color-info));
--vip-card-soft: color-mix(in srgb, var(--aksrt-indigo, var(--color-info)) 12%, transparent);
}
.uc-vip-tab .uc-vip-level-card.zib-widget:hover,
.uc-vip-tab .uc-vip-level-card.pay-vip-info:hover {
border-color: color-mix(in srgb, var(--vip-card-accent) 42%, var(--color-line-soft));
box-shadow: var(--shadow);
transform: translateY(-2px);
}
.uc-vip-tab .uc-vip-level-card .vip-baicon {
color: var(--vip-card-accent);
opacity: 0.1;
}
.uc-vip-level-card__inner {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: stretch;
min-height: 100%;
}
.uc-vip-tab .uc-vip-level-card .v-iconbox {
justify-content: flex-start;
gap: var(--space-3, 12px);
margin-bottom: var(--space-4, 16px);
}
.uc-vip-tab .uc-vip-level-card .v-icon {
width: 40px;
height: 40px;
flex: 0 0 auto;
border: 1px solid color-mix(in srgb, var(--vip-card-accent) 28%, transparent);
border-radius: var(--radius-xl, 12px);
background: color-mix(in srgb, var(--vip-card-accent) 14%, transparent);
color: var(--vip-card-accent);
}
.uc-vip-tab .uc-vip-level-card.level-1 .v-name,
.uc-vip-tab .uc-vip-level-card.level-2 .v-name {
color: var(--vip-card-accent);
}
.uc-vip-tab .uc-vip-level-card .v-name {
font-size: var(--text-md, 1.125rem);
line-height: var(--leading-tight, 1.2);
}
.uc-vip-tab .uc-vip-level-card .v-desc {
margin-top: 4px;
color: var(--color-ink-3);
line-height: var(--leading-snug, 1.4);
}
.uc-vip-tab .uc-vip-level-card .v-equity {
flex: 1 1 auto;
max-width: none;
margin: 0 0 var(--space-5, 20px) !important;
color: var(--color-ink-3);
}
.uc-vip-tab .uc-vip-level-card .v-equity > li {
margin-bottom: var(--space-2, 8px) !important;
}
.uc-vip-tab .uc-vip-cta {
display: flex !important;
align-items: center;
justify-content: center;
gap: var(--space-2, 8px);
width: 100%;
min-width: 0;
min-height: var(--btn-lg-h, 44px);
margin-top: auto;
padding: 10px 16px !important;
border: 0;
border-radius: var(--radius, 8px);
font-weight: 700;
line-height: 1.2;
text-align: center;
text-decoration: none;
transition: filter var(--dur-fast, 0.15s) var(--ease, ease), box-shadow var(--dur-fast, 0.15s) var(--ease, ease), transform var(--dur-fast, 0.15s) var(--ease, ease);
}
.uc-vip-tab .uc-vip-cta:hover {
filter: brightness(0.97) saturate(1.08);
box-shadow: var(--shadow-sm);
opacity: 1;
transform: translateY(-1px);
}
.uc-vip-tab .uc-vip-cta:focus-visible {
outline: 3px solid var(--color-primary);
outline-offset: 3px;
box-shadow: var(--focus-ring);
}
.uc-vip-tab .uc-vip-cta--current {
cursor: default;
background: var(--surface-3) !important;
color: var(--color-ink-3) !important;
filter: none;
opacity: 1;
}
.uc-vip-tab .uc-vip-cta--current:hover {
box-shadow: none;
transform: none;
}
.uc-vip-tab .uc-vip-points-link.zib-widget {
margin: 0 0 var(--space-5, 20px);
border: 1px solid color-mix(in srgb, var(--color-warning) 22%, var(--color-line-soft));
border-radius: var(--radius-lg, var(--radius, 8px));
background-color: var(--surface-1);
background-image: linear-gradient(135deg, color-mix(in srgb, var(--color-warning) 8%, transparent), transparent 70%);
box-shadow: none;
}
.uc-vip-tab > .muted-box {
margin-top: 0;
border: 1px solid var(--color-line-soft);
background: var(--surface-4);
}
@media (max-width: 767px) {
.uc-vip-summary {
padding: 10px 12px;
padding: 18px 14px;
padding-inline-end: 76px;
}
.uc-vip-summary__watermark {
font-size: 110px;
opacity: 0.12;
inset-inline-end: -0.14em;
font-size: clamp(82px, 27vw, 112px);
opacity: 0.08;
}
.uc-vip-status {
column-gap: 12px;
gap: var(--space-2, 8px);
}
.uc-vip-status__label {
font-size: var(--text-md, 1.125rem);
}
.uc-vip-level-cards {
grid-template-columns: 1fr;
gap: var(--space-3, 12px);
}
.uc-vip-tab .uc-vip-level-card.zib-widget,
.uc-vip-tab .uc-vip-level-card.pay-vip-info {
padding: 20px 16px 16px;
}
}
@media (max-width: 420px) {
.uc-vip-summary {
padding-inline-end: 62px;
}
.uc-vip-summary__watermark {
font-size: 86px;
}
}
@media (prefers-reduced-motion: reduce) {
.uc-vip-summary,
.uc-vip-tab .uc-vip-level-card.zib-widget,
.uc-vip-tab .uc-vip-level-card.pay-vip-info,
.uc-vip-tab .uc-vip-cta {
transition: none;
}
.uc-vip-tab .uc-vip-level-card.zib-widget:hover,
.uc-vip-tab .uc-vip-level-card.pay-vip-info:hover,
.uc-vip-tab .uc-vip-cta:hover {
transform: none;
}
}
+55 -37
View File
@@ -26,55 +26,62 @@ $levels = is_array($vip_settings['levels'] ?? null) ? $vip_settings['levels'] :
$desc_top = (string) ($vip_settings['desc'] ?? '');
// Membership summary: title → level → intro → expiry.
$status_html = '';
$vip_state = 'unopened';
$vip_state_label = __('未开通', 'aksrt');
$vip_level_number = 0;
$vip_level_name = __('未开通会员', 'aksrt');
$vip_description = $desc_top !== '' ? $desc_top : __('开通会员,尊享专属特权', 'aksrt');
$vip_expiry_text = __('到期时间:未开通', 'aksrt');
$vip_is_permanent = false;
$btn_type = 'pay';
if ($level > 0) {
$lv_name = (string) ($levels[$level]['name'] ?? ('VIP' . $level));
$lv_desc = (string) ($levels[$level]['desc'] ?? '');
$vip_level_number = $level;
$vip_level_name = (string) ($levels[$level]['name'] ?? ('VIP' . $level));
$level_desc = (string) ($levels[$level]['desc'] ?? '');
$vip_description = $level_desc !== '' ? $level_desc : __('尊享专属特权', 'aksrt');
if ($expires <= 0) {
$expiry_text = __('到期时间:永久会员', 'aksrt');
$expiry_class = 'c-green';
$vip_state = 'permanent';
$vip_state_label = __('永久有效', 'aksrt');
$vip_expiry_text = __('到期时间:永久会员', 'aksrt');
$vip_is_permanent = true;
if ($level < 2) {
$btn_type = 'upgrade';
}
} else {
$expiry_text = sprintf(__('到期时间:%s', 'aksrt'), wp_date('Y年m月d日', $expires));
$expiry_class = 'c-yellow';
$vip_state = 'active';
$vip_state_label = __('生效中', 'aksrt');
$vip_expiry_text = sprintf(__('到期时间:%s', 'aksrt'), wp_date('Y年m月d日', $expires));
$btn_type = 'renew_' . $level;
}
$status_html = '<div class="uc-vip-status">'
. '<div class="uc-vip-status__label">' . esc_html__('我的会员', 'aksrt') . '</div>'
. '<div class="uc-vip-status__main"><i class="fas fa-crown" aria-hidden="true"></i><span class="font-bold">' . esc_html($lv_name) . '</span></div>'
. '<div class="uc-vip-status__desc muted-color">' . esc_html($lv_desc !== '' ? $lv_desc : __('尊享专属特权', 'aksrt')) . '</div>'
. '<div class="uc-vip-status__expiry ' . esc_attr($expiry_class) . '">' . esc_html($expiry_text) . '</div>'
. '</div>';
} elseif ($expired_level > 0) {
$expired_name = (string) ($levels[$expired_level]['name'] ?? ('VIP' . $expired_level));
$vip_state = 'expired';
$vip_state_label = __('已过期', 'aksrt');
$vip_level_number = $expired_level;
$vip_level_name = (string) ($levels[$expired_level]['name'] ?? ('VIP' . $expired_level));
$expired_desc = (string) ($levels[$expired_level]['desc'] ?? '');
if ($expired_desc === '') {
$expired_desc = $desc_top !== '' ? $desc_top : __('会员已过期', 'aksrt');
}
$expiry_text = $expires > 0
$vip_description = $expired_desc !== ''
? $expired_desc
: ($desc_top !== '' ? $desc_top : __('会员已过期', 'aksrt'));
$vip_expiry_text = $expires > 0
? sprintf(__('到期时间:%s', 'aksrt'), wp_date('Y年m月d日', $expires))
: __('到期时间:已过期', 'aksrt');
$status_html = '<div class="uc-vip-status">'
. '<div class="uc-vip-status__label">' . esc_html__('我的会员', 'aksrt') . '</div>'
. '<div class="uc-vip-status__main c-red"><i class="fas fa-crown" aria-hidden="true"></i><span class="font-bold">' . esc_html($expired_name) . '</span></div>'
. '<div class="uc-vip-status__desc muted-color">' . esc_html($expired_desc) . '</div>'
. '<div class="uc-vip-status__expiry c-red">' . esc_html($expiry_text) . '</div>'
. '</div>';
$btn_type = 'pay';
} else {
// Never opened VIP: keep the same four-line summary shape.
$status_html = '<div class="uc-vip-status">'
. '<div class="uc-vip-status__label">' . esc_html__('我的会员', 'aksrt') . '</div>'
. '<div class="uc-vip-status__main c-red"><i class="fas fa-crown" aria-hidden="true"></i><span class="font-bold">' . esc_html__('未开通会员', 'aksrt') . '</span></div>'
. '<div class="uc-vip-status__desc muted-color">' . esc_html($desc_top !== '' ? $desc_top : __('开通会员,尊享专属特权', 'aksrt')) . '</div>'
. '<div class="uc-vip-status__expiry muted-2-color">' . esc_html__('到期时间:未开通', 'aksrt') . '</div>'
. '</div>';
$btn_type = 'pay';
}
$status_html = '<div class="uc-vip-status" role="status">'
. '<div class="uc-vip-status__heading">'
. '<h2 class="uc-vip-status__label">' . esc_html__('我的会员', 'aksrt') . '</h2>'
. '<span class="uc-vip-status__state">' . esc_html($vip_state_label) . '</span>'
. '</div>'
. '<div class="uc-vip-status__main uc-vip-status__main--level-' . (int) $vip_level_number . '">'
. '<span class="uc-vip-status__icon" aria-hidden="true"><i class="fas fa-crown"></i></span>'
. '<span class="uc-vip-status__level">' . esc_html($vip_level_name) . '</span>'
. '</div>'
. '<div class="uc-vip-status__desc">' . esc_html($vip_description) . '</div>'
. '<div class="uc-vip-status__expiry"><i class="fas fa-clock" aria-hidden="true"></i><span>' . esc_html($vip_expiry_text) . '</span></div>'
. '</div>';
// Level cards.
$cards = '';
foreach ([1, 2] as $lv) {
@@ -90,6 +97,7 @@ foreach ([1, 2] as $lv) {
: '';
$cta = sprintf(__('开通%s', 'aksrt'), $lv_name);
$vip_attr = $lv;
$cta_is_current = false;
if (str_starts_with($btn_type, 'renew_')) {
$renew_lv = (int) substr($btn_type, 6);
if ($lv === $renew_lv) {
@@ -108,8 +116,13 @@ foreach ([1, 2] as $lv) {
} elseif ($level > 0 && $lv < $level) {
continue;
} elseif ($level > 0 && $lv === $level) {
if ($vip_is_permanent) {
$cta = __('当前等级', 'aksrt');
$cta_is_current = true;
} else {
$cta = sprintf(__('续费%s', 'aksrt'), $lv_name);
}
}
$cards .= '<div class="zib-widget text-center pay-vip-info flex1 level-' . $lv . ' uc-vip-level-card">';
// Decorative gem: do NOT use .abs-center (that utility is full-width header/search placement).
@@ -123,9 +136,14 @@ foreach ([1, 2] as $lv) {
}
$cards .= '</div></div>';
$cards .= $equity;
$cards .= '<a class="padding-lg but jb-vip' . $lv . ' radius pay-vip uc-vip-cta" href="javascript:;" vip-level="' . (int) $vip_attr . '">'
if ($cta_is_current) {
$cards .= '<span class="padding-lg but jb-vip' . $lv . ' radius uc-vip-cta uc-vip-cta--current" aria-current="true">'
. '<span>' . esc_html($cta) . '</span></span>';
} else {
$cards .= '<a class="padding-lg but jb-vip' . $lv . ' radius pay-vip uc-vip-cta" href="javascript:;" vip-level="' . (int) $vip_attr . '" data-vip-level="' . (int) $vip_attr . '">'
. '<span>' . esc_html($cta) . '</span>'
. ' <i class="fas fa-chevron-circle-right" aria-hidden="true"></i></a>';
}
$cards .= '</div></div>';
}
@@ -134,13 +152,13 @@ $points_html = '';
if ($level <= 0 && !empty($vip_settings['points_exchange']['enabled'])) {
$points_html = '<div class="c-yellow zib-widget flex ac jsb uc-vip-points-link mt10">'
. '<div class="flex1 em12 flex ac"><i class="fas fa-coins" aria-hidden="true"></i><span class="ml6">' . esc_html__('积分兑换会员', 'aksrt') . '</span></div>'
. '<a class="badg radius c-yellow pay-vip" href="javascript:;" vip-level="1">'
. '<a class="badg radius c-yellow pay-vip" href="javascript:;" vip-level="1" data-vip-level="1">'
. esc_html__('立即兑换', 'aksrt')
. ' <i class="fas fa-chevron-circle-right" aria-hidden="true"></i></a></div>';
}
?>
<div class="uc-vip-tab" data-aksrt-vip-tab>
<div class="uc-vip-summary" data-aksrt-vip-summary>
<div class="uc-vip-summary" data-aksrt-vip-summary data-vip-state="<?php echo esc_attr($vip_state); ?>" data-vip-level="<?php echo (int) $vip_level_number; ?>" data-vip-permanent="<?php echo $vip_is_permanent ? 'true' : 'false'; ?>">
<div class="uc-vip-summary__watermark" aria-hidden="true"><i class="fas fa-crown"></i></div>
<?php echo $status_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>