fix(frontend): keep user center and user home avatar frames rounded

The two large hero avatar frames go back to their legacy rounded-square look: the user center profile header keeps 6px and the public user home hero keeps var(--radius). Both declare an explicit radius because the global avatar primitive (img.avatar, 0,1,1) ties them on specificity and only the later route rule wins the cascade.

Everything else stays: the primitive still circles the bare avatar-mini in the ban and report modals, the VIP modal avatar and the post-card meta avatar, the friends page avatar is circular, and the VIP icon still leads the badge row. The contract test now guards the two hero frames as rounded-square so the primitive cannot silently re-circularize them.
This commit is contained in:
2026-10-10 13:59:47 +08:00
parent 9cb5e7bea3
commit a3b708f9d4
3 changed files with 23 additions and 13 deletions
+4 -2
View File
@@ -125,7 +125,9 @@
flex-shrink: 0; flex-shrink: 0;
width: 140px; width: 140px;
height: 140px; height: 140px;
border-radius: var(--radius-avatar, 50%); /* 用户中心头图卡保持 legacy 圆角方形:显式声明圆角,避免被 public 捆绑里
同特异性(0,1,1)的全局头像原语 img.avatar 抢先。 */
border-radius: 6px;
} }
#aksrt-bg-file-input[hidden], #aksrt-bg-file-input[hidden],
@@ -133,7 +135,7 @@
display: none !important; display: none !important;
} }
/* 140px 正方形 → 正圆:统一前端用户头像形状(--radius-avatar) */ /* 对齐 legacy: 140px, 6px 圆角 */
.uc-header__avatar img { .uc-header__avatar img {
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
+3 -1
View File
@@ -88,7 +88,9 @@
.aksrt-author-hero__avatar img { .aksrt-author-hero__avatar img {
width: 120px; width: 120px;
height: 120px; height: 120px;
border-radius: var(--radius-avatar, 50%); /* 公开主页 hero 头像保持 legacy 圆角方形:显式声明圆角,避免被 public 捆绑里
同特异性(0,1,1)的全局头像原语 img.avatar 抢先。 */
border-radius: var(--radius);
border: 4px solid var(--surface-1); border: 4px solid var(--surface-1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
object-fit: cover; object-fit: cover;
@@ -8,10 +8,11 @@ const test = require('node:test');
const root = path.resolve(__dirname, '..', '..'); const root = path.resolve(__dirname, '..', '..');
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8'); const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
// --radius-avatar is the single source of truth for frontend avatar shape. // --radius-avatar is the single source of truth for frontend avatar shape,
// Route CSS loads after the public bundle, and `.uc-header__avatar img` / // except the two large hero frames on the user center and the public user
// `.aksrt-author-hero__avatar img` tie the primitive at 0,1,1 specificity, // home, which intentionally keep their legacy rounded-square look. Route CSS
// so every framed avatar must opt in explicitly. // loads after the public bundle and ties the primitive at 0,1,1 specificity,
// so each frame must declare its own radius to win the cascade.
const primitiveSelectors = [ const primitiveSelectors = [
'img.avatar,', 'img.avatar,',
'img.avatar-mini,', 'img.avatar-mini,',
@@ -37,18 +38,23 @@ test('the global avatar shape primitive renders every avatar image circular', ()
assert.ok(sizeSystem >= 0 && sizeSystem < start, 'primitive must sit after the avatar size system'); assert.ok(sizeSystem >= 0 && sizeSystem < start, 'primitive must sit after the avatar size system');
}); });
test('user center and public user home avatar frames are circular', () => { test('the user center and public user home keep their legacy rounded-square avatar frames', () => {
const profile = read('assets/css/routes/profile/layout.css'); const profile = read('assets/css/routes/profile/layout.css');
const userHome = read('assets/css/routes/user-home.css'); const userHome = read('assets/css/routes/user-home.css');
const friends = read('assets/css/routes/friends.css');
assert.match(profile, /\.uc-header__avatar\s*\{[^}]*border-radius:\s*var\(--radius-avatar,\s*50%\)/s); // Both frames must keep an explicit radius: the global primitive ties them at
assert.doesNotMatch(profile, /\.uc-header__avatar\s*\{[^}]*border-radius:\s*6px/s); // 0,1,1 specificity, so only the later route rule keeps them square.
assert.match(profile, /\.uc-header__avatar\s*\{[^}]*border-radius:\s*6px/s);
assert.doesNotMatch(profile, /\.uc-header__avatar\s*\{[^}]*--radius-avatar/s);
// img and the upload overlay both inherit the frame radius. // img and the upload overlay both inherit the frame radius.
assert.match(profile, /\.uc-header__avatar img\s*\{[^}]*border-radius:\s*inherit/s); assert.match(profile, /\.uc-header__avatar img\s*\{[^}]*border-radius:\s*inherit/s);
assert.match(userHome, /\.aksrt-author-hero__avatar img\s*\{[^}]*border-radius:\s*var\(--radius-avatar,\s*50%\)/s); assert.match(userHome, /\.aksrt-author-hero__avatar img\s*\{[^}]*border-radius:\s*var\(--radius\)/s);
assert.doesNotMatch(userHome, /\.aksrt-author-hero__avatar img\s*\{[^}]*border-radius:\s*var\(--radius\)/s); assert.doesNotMatch(userHome, /\.aksrt-author-hero__avatar img\s*\{[^}]*--radius-avatar/s);
});
test('every other frontend avatar frame is circular', () => {
const friends = read('assets/css/routes/friends.css');
assert.match(friends, /\.friend-card__avatar\s*\{[^}]*border-radius:\s*var\(--radius-avatar,\s*50%\)/s); assert.match(friends, /\.friend-card__avatar\s*\{[^}]*border-radius:\s*var\(--radius-avatar,\s*50%\)/s);
assert.doesNotMatch(friends, /\.friend-card__avatar\s*\{[^}]*border-radius:\s*var\(--radius\)/s); assert.doesNotMatch(friends, /\.friend-card__avatar\s*\{[^}]*border-radius:\s*var\(--radius\)/s);