diff --git a/assets/css/frontend/pages/base-1.css b/assets/css/frontend/pages/base-1.css
index 2cccdec1..51b25726 100644
--- a/assets/css/frontend/pages/base-1.css
+++ b/assets/css/frontend/pages/base-1.css
@@ -166,56 +166,6 @@ html.dark .archive-timeline__badge {
font-size: 13px;
}
-/* 社交登录分隔线 */
-.social-login-divider {
- display: flex;
- align-items: center;
- margin: 24px 0 16px;
- color: var(--color-ink-3, #94a3b8);
- font-size: 13px;
-}
-.social-login-divider::before,
-.social-login-divider::after {
- content: '';
- flex: 1;
- height: 1px;
- background: var(--color-line, #e2e8f0);
-}
-.social-login-divider span {
- padding: 0 16px;
- white-space: nowrap;
-}
-
-.oauth-row {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- justify-content: center;
- margin-top: 16px;
-}
-
-.oauth-btn {
- width: 44px;
- height: 44px;
- border-radius: var(--radius-btn);
- border: 1px solid var(--color-line);
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--color-ink-3);
- transition: border-color 0.2s, color 0.2s;
-}
-.oauth-btn i {
- font-size: 22px;
- line-height: 1;
- display: block;
-}
-
-.oauth-btn:hover {
- border-color: var(--color-primary);
- color: var(--color-primary);
-}
-
.auth-footer-link {
text-align: center;
margin-top: 20px;
diff --git a/assets/css/routes/user-center/settings.css b/assets/css/routes/user-center/settings.css
index 9dafdad8..fbdc466f 100644
--- a/assets/css/routes/user-center/settings.css
+++ b/assets/css/routes/user-center/settings.css
@@ -265,7 +265,8 @@
.aksrt-account-list--social .binding-item { border: 0; }
.aksrt-account-list--social .aksrt-account-row { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto auto; text-decoration: none; }
.aksrt-account-list--social .binding-item__icon { display: grid; place-items: center; width: 24px; height: 24px; color: var(--color-ink, #333); }
-.aksrt-account-list--social .binding-item__icon i { font-size: 22px; line-height: 1; }
+.aksrt-account-list--social .binding-item__icon i,
+.aksrt-account-list--social .binding-item__icon svg { font-size: 22px; line-height: 1; }
.aksrt-account-list--social .binding-status { display: inline-flex; align-items: center; gap: 4px; color: var(--color-success, #16a34a); font-size: 11px; white-space: nowrap; }
.aksrt-account-list--social .binding-status--idle { color: var(--muted-2-color, var(--color-ink-4, #999)); }
.aksrt-account-list--social .binding-unbind-btn { color: var(--this-color, #e4a11b); }
diff --git a/inc/helpers/aksrt-icons.php b/inc/helpers/aksrt-icons.php
index 828498b8..2d2d1b8d 100644
--- a/inc/helpers/aksrt-icons.php
+++ b/inc/helpers/aksrt-icons.php
@@ -42,7 +42,7 @@ endif;
if (!function_exists('aksrt_brand_icon_map')) :
/**
- * Short key → FA5 class string (allowlisted).
+ * Short key → FA5 class string or built-in SVG sprite key (allowlisted).
*
* @return array
*/
@@ -52,10 +52,12 @@ function aksrt_brand_icon_map(): array {
return $map;
}
- // FA5 Free has no bilibili/gitee brands — use solid stand-ins.
+ // FA5 Free ships no Gitee brand glyph and no multi-colour Google mark, so
+ // those two resolve to the theme's built-in SVG sprite (zibsvg-*) instead of
+ // a solid stand-in; every other key stays on FA5.
$map = [
'github' => 'fa fab fa-github',
- 'google' => 'fa fab fa-google',
+ 'google' => 'zibsvg-google-color',
'x' => 'fa fab fa-twitter',
'twitter' => 'fa fab fa-twitter',
'email' => 'fa fas fa-envelope',
@@ -73,7 +75,7 @@ function aksrt_brand_icon_map(): array {
'instagram' => 'fa fab fa-instagram',
'web' => 'fa fas fa-globe',
'globe' => 'fa fas fa-globe',
- 'gitee' => 'fa fas fa-code-branch',
+ 'gitee' => 'zibsvg-gitee',
'baidu' => 'fa fas fa-paw',
'alipay' => 'fa fab fa-alipay',
];
@@ -127,10 +129,10 @@ endif;
if (!function_exists('aksrt_brand_icon_class')) :
/**
- * Resolve a short key or FA class string to an allowlisted FA5 class.
+ * Resolve a short key, FA class string or sprite key to its icon value.
*
- * @param string $name Short key or full FA class string.
- * @return string e.g. 'fa fab fa-github', or '' if unknown.
+ * @param string $name Short key, full FA class string or zibsvg-/customsvg- value.
+ * @return string e.g. 'fa fab fa-github' or 'zibsvg-gitee', or '' if unknown.
*/
function aksrt_brand_icon_class(string $name): string {
$raw = trim($name);
@@ -138,6 +140,12 @@ function aksrt_brand_icon_class(string $name): string {
return '';
}
+ // Built-in SVG sprite and inline SVG values pass straight through; they are
+ // not FA class strings and must survive to the markup renderer untouched.
+ if (preg_match('/^(?:zibsvg|customsvg)-/i', $raw)) {
+ return $raw;
+ }
+
// Pass through already-valid full FA class strings (footer contact_more).
if (preg_match('/^fa\s+(?:fas|far|fab)\s+fa-[\w-]+(?:\s+fa-[\w-]+)*$/i', $raw)) {
// Prefer mapped short key when strip matches our catalog (normalizes weixin etc.).
@@ -164,14 +172,21 @@ endif;
if (!function_exists('aksrt_brand_icon')) :
/**
- * Escaped FA markup for a brand/contact icon.
+ * Escaped markup for a brand/contact icon (FA5 or built-in SVG sprite).
*
- * @param string $name Short key or FA class string.
+ * @param string $name Short key, FA class string or zibsvg-/customsvg- value.
* @param array|string $extra_classes Optional extra classes.
* @return string Safe HTML or empty string when unknown.
*/
function aksrt_brand_icon(string $name, $extra_classes = []): string {
$class = aksrt_brand_icon_class($name);
+
+ // Sprite and inline-SVG values carry their own element, so the shared icon
+ // renderer owns the markup (it emits
'
+ : '';
+ $html .= '';
}
public function get_frontend_bindings_data(int $user_id): array {
diff --git a/src/WidgetSystem/Renderers/UserRenderer.php b/src/WidgetSystem/Renderers/UserRenderer.php
index 2bc8bd40..131e6c64 100644
--- a/src/WidgetSystem/Renderers/UserRenderer.php
+++ b/src/WidgetSystem/Renderers/UserRenderer.php
@@ -125,7 +125,7 @@ final class UserRenderer {
if ($name === '' || $style_name === '' || $label === '' || $url === '') {
continue;
}
- $html .= '' . (string) ($item['icon'] ?? '') . '';
+ $html .= '' . (string) ($item['icon'] ?? '') . '';
}
return $html . '';
}
diff --git a/templates/parts/auth-content.php b/templates/parts/auth-content.php
index 0bbcf171..87999052 100644
--- a/templates/parts/auth-content.php
+++ b/templates/parts/auth-content.php
@@ -147,6 +147,5 @@ $oauth_bind_pass = $oauth_intent === 'bind'
-
diff --git a/tests/widget-system/auth-source-contract.test.cjs b/tests/widget-system/auth-source-contract.test.cjs
index 9f7c2031..0e915734 100644
--- a/tests/widget-system/auth-source-contract.test.cjs
+++ b/tests/widget-system/auth-source-contract.test.cjs
@@ -239,3 +239,58 @@ test('OAuth frontend is accessible, redirect-aware, and removes retired provider
assert.doesNotMatch(signCss + mainCss, /social-login-item[^\n{]*(?:baidu|alipay)|social-login-item\.(?:baidu|alipay)/i);
assert.match(mainCss, /\.pay-box \.but\.baidu/);
});
+
+test('the social-login area drops the implicit agreement line', () => {
+ const shared = read('templates/parts/auth-content.php');
+ const userRenderer = read('src/WidgetSystem/Renderers/UserRenderer.php');
+
+ // The reference shows no agreement copy under the social buttons; only the
+ // registration panel keeps its own, and it stays reachable.
+ assert.doesNotMatch(shared, /使用社交账号登录即表示/);
+ assert.match(shared, /注册即表示/);
+ assert.match(shared, /name="agree_terms"[\s\S]*?required/);
+ assert.doesNotMatch(userRenderer, /使用社交账号登录即表示|我已阅读并同意/);
+});
+
+test('brand icons resolve Gitee and Google to the reference SVG sprite', () => {
+ const icons = read('inc/helpers/aksrt-icons.php');
+ const sprites = read('assets/js/runtime/svg-icon.js');
+
+ // FA5 Free ships no Gitee brand glyph and no multi-colour Google mark, so both
+ // resolve to the theme's own symbol sheet exactly like the reference does.
+ assert.match(icons, /'gitee'\s*=>\s*'zibsvg-gitee'/);
+ assert.match(icons, /'google'\s*=>\s*'zibsvg-google-color'/);
+ assert.match(icons, /'github'\s*=>\s*'fa fab fa-github'/);
+ assert.match(icons, /'wechat'\s*=>\s*'fa fab fa-weixin'/);
+ assert.match(sprites, /aksrt_svgs\['gitee'\]/);
+ assert.match(sprites, /aksrt_svgs\['google-color'\]/);
+ // Sprite values must reach the shared icon renderer instead of being wrapped
+ // in a bogus .
+ assert.match(icons, /strpos\(\$class, 'zibsvg-'\) === 0[\s\S]*?aksrt_icon_markup\(\$class, \$extra_classes\)/);
+});
+
+test('the top-nav user popover reproduces the reference quick-login block', () => {
+ const oauth = read('src/Auth/OAuth.php');
+ const header = read('inc/helpers/header/aksrt-header-navbar.php');
+ const links = read('inc/helpers/aksrt-links.php');
+ const legacyCss = read('assets/css/frontend/pages/base-1.css');
+
+ assert.match(oauth, /social-separator separator muted-3-color em09 mt10/);
+ assert.match(oauth, /''/);
+ assert.match(oauth, /'social-login-item ' \. \$type/);
+ assert.match(oauth, /' button-lg' : ' toggle-radius'/);
+ assert.match(oauth, /rel="nofollow"/);
+ assert.match(oauth, /qrcode-signin/);
+ assert.match(oauth, /oauth_button_lg/);
+
+ // Callers append the block verbatim — no hand-rolled separator or wrapper.
+ assert.match(header, /render_social_buttons\(__\('快速登录', 'aksrt'\)\)/);
+ assert.doesNotMatch(header, /social-separator|social_loginbar/);
+ assert.match(links, /render_social_buttons\(__\('使用以下方式快速登录', 'aksrt'\)\)/);
+ assert.doesNotMatch(links, /social_loginbar/);
+
+ // The retired .social-login-divider / .oauth-row / .oauth-btn markup and its
+ // stylesheet are gone.
+ assert.doesNotMatch(oauth, /oauth-row|oauth-btn|social-login-divider/);
+ assert.doesNotMatch(legacyCss, /\.oauth-row|\.oauth-btn|\.social-login-divider/);
+});
diff --git a/tests/widget-system/user-card-runtime-contract.test.cjs b/tests/widget-system/user-card-runtime-contract.test.cjs
index 1d5b77c0..6413e52b 100644
--- a/tests/widget-system/user-card-runtime-contract.test.cjs
+++ b/tests/widget-system/user-card-runtime-contract.test.cjs
@@ -23,9 +23,13 @@ test('user and author widgets render the reference state contract', () => {
assert.match(data.guest, /signup-loader ml10 but jb-yellow padding-lg/)
assert.match(data.guest, /social-separator separator muted-3-color em09 mt20 mb20/)
assert.match(data.guest, /social_loginbar/)
- assert.match(data.guest, /class="social-login-item github"/)
- assert.match(data.guest, /class="social-login-item qq" data-provider="clogin_qq"/)
+ assert.match(data.guest, /class="social-login-item github toggle-radius"/)
+ assert.match(data.guest, /class="social-login-item qq toggle-radius" data-provider="clogin_qq"/)
+ assert.match(data.guest, /title="GitHub登录"/)
assert.doesNotMatch(data.guest, /aksrt-widget|aksrt-panel-wrap|user-card__auth-btns|oauth-row|oauth-btn/)
+ // The social-login area carries no agreement copy; the reference guest card
+ // only shows the separator, the .social_loginbar and the provider buttons.
+ assert.doesNotMatch(data.guest, /我已阅读并同意|使用社交账号登录即表示|注册即表示/)
assert.match(data.guest_no_registration, /signin-loader but jb-blue padding-lg/)
assert.doesNotMatch(data.guest_no_registration, /signup-loader/)