fix(pricing): align follow-up acceptance details (#7228)

* fix(pricing): unify model display order

* fix(pricing): scope banner colors and footnote size

* fix(pricing): render all access checks consistently

* fix(pricing): center model access icons
This commit is contained in:
Ray Xi
2026-08-21 07:21:47 +00:00
committed by GitHub
parent d343775fa7
commit 105dcf3764
8 changed files with 78 additions and 94 deletions
@@ -50,7 +50,7 @@ const apiOrigin =
min-height: 48px;
padding: 7px 70px;
color: #20241d;
background: #e6efd5;
background: #d8ffb5;
border: 0;
}
.go-banner[hidden] { display: none !important; }
@@ -87,44 +87,19 @@ const unlimitedByTier: Record<TierId, Set<string>> = {
max: new Set(popularModels.map((model) => model.name)),
};
const popularOrderByTier: Record<TierId, string[]> = {
go: [
'DeepSeek V4 Flash',
'DeepSeek V4 Pro',
'GLM-5.2',
'Kimi K2.7 Code',
'MiniMax M2.7',
'Kimi K2.6',
'MiMo V2.5 Pro',
'GLM-5.1',
],
plus: ['DeepSeek V4 Flash', 'DeepSeek V4 Pro', 'GLM-5.2', 'Kimi K2.7 Code'],
pro: [
'DeepSeek V4 Flash',
'DeepSeek V4 Pro',
'GLM-5.2',
'Kimi K2.7 Code',
'MiniMax M2.7',
],
max: [
'DeepSeek V4 Flash',
'DeepSeek V4 Pro',
'GLM-5.2',
'Kimi K2.7 Code',
'MiniMax M2.7',
'Kimi K2.6',
'MiMo V2.5 Pro',
'GLM-5.1',
],
};
const popularModelDisplayOrder = [
'DeepSeek V4 Flash',
'DeepSeek V4 Pro',
'GLM-5.2',
'Kimi K2.7 Code',
'MiMo V2.5 Pro',
'MiniMax M2.7',
'Kimi K2.6',
'GLM-5.1',
];
const popularOrderForTier = (tier: TierId) => {
const lead = popularOrderByTier[tier];
return [
...lead.map((name) => popularModels.find((model) => model.name === name)!),
...popularModels.filter((model) => !lead.includes(model.name)),
];
};
const orderedPopularModels = () =>
popularModelDisplayOrder.map((name) => popularModels.find((model) => model.name === name)!);
const renderCatalogLabel = (label: string) => fillTemplate(label, {
skillsCount,
@@ -273,16 +248,7 @@ const usageRows = [
{ ...popularModel('GLM-5.1'), value: 670 },
];
const comparisonPopular = [
'DeepSeek V4 Flash',
'DeepSeek V4 Pro',
'GLM-5.2',
'Kimi K2.7 Code',
'MiniMax M2.7',
'Kimi K2.6',
'MiMo V2.5 Pro',
'GLM-5.1',
].map((name) => popularModels.find((model) => model.name === name)!);
const comparisonPopular = orderedPopularModels();
const comparisonFlagship = [
'Kimi-K3',
@@ -356,7 +322,7 @@ const otherModels = 'Gemini-3.1-Pro, Grok-4.5, GPT-5.4, GPT-5.6-Terra, Qwen3.7-M
<div class="benefits-summary-stage">
<div class="four-tier-grid decision-grid">
{cardData.map(({ tier, logo, view }) => {
const popular = popularOrderForTier(tier);
const popular = orderedPopularModels();
const flagship = tier === 'plus' ? plusFlagshipModels : flagshipModels;
const imageResolution = tier === 'plus' ? '1K' : tier === 'pro' ? '2K' : '4K';
const bonusPct = tier === 'go' ? null : CREDIT_BONUS_PCT[tier];
@@ -566,7 +532,8 @@ const otherModels = 'Gemini-3.1-Pro, Grok-4.5, GPT-5.4, GPT-5.6-Terra, Qwen3.7-M
<td class="model-name-cell"><span class:list={['model-logo', model.iconClass]}><img src={model.icon} alt="" loading="lazy" /></span><span>{model.name}</span></td>
{(['go', 'plus', 'pro', 'max'] as TierId[]).map((tier) => {
const status = accessStatus(category, model.name, tier);
return status.kind === 'more-ample' ? (
const checkOnly = status.kind === 'more-ample' || status.kind === 'ample' || status.kind === 'included';
return checkOnly ? (
<td><span class={`model-access-status ${status.kind}`} aria-label={status.text}><i class="status-icon check"></i></span></td>
) : (
<td><span class={`model-access-status ${status.kind}`}><i class:list={['status-icon', { infinity: status.kind === 'unlimited' }]}>{status.kind === 'unlimited' ? '∞' : ''}</i>{status.text}</span></td>
@@ -676,7 +643,7 @@ const otherModels = 'Gemini-3.1-Pro, Grok-4.5, GPT-5.4, GPT-5.6-Terra, Qwen3.7-M
line-height: 1;
letter-spacing: 0.055em;
text-align: center;
background: #d8ffb5;
background: var(--pricing-accent);
border-radius: 12px 12px 0 0;
}
@@ -1598,7 +1565,12 @@ const otherModels = 'Gemini-3.1-Pro, Grok-4.5, GPT-5.4, GPT-5.6-Terra, Qwen3.7-M
white-space: nowrap;
}
.model-access-status.more-ample {
.model-access-status.more-ample,
.model-access-status.ample,
.model-access-status.included {
display: grid;
grid-template-columns: 1fr;
place-items: center;
width: 104px;
height: 23px;
}
@@ -1632,26 +1604,6 @@ const otherModels = 'Gemini-3.1-Pro, Grok-4.5, GPT-5.4, GPT-5.6-Terra, Qwen3.7-M
transform: translateY(-1px) rotate(45deg);
}
.model-access-status.ample,
.model-access-status.included {
width: 104px;
height: 23px;
overflow: hidden;
font-size: 0;
}
.model-access-status.ample::after,
.model-access-status.included::after {
grid-column: 1;
justify-self: center;
width: 5px;
height: 9px;
content: '';
border: 2px solid;
border-width: 0 2px 2px 0;
transform: translateY(-1px) rotate(45deg);
}
.model-access-status.unavailable {
position: relative;
width: 104px;
@@ -1664,7 +1616,7 @@ const otherModels = 'Gemini-3.1-Pro, Grok-4.5, GPT-5.4, GPT-5.6-Terra, Qwen3.7-M
.model-access-status.unavailable::after {
position: absolute;
top: 50%;
left: 11.5px;
left: 50%;
width: 9px;
height: 1.5px;
content: '';
@@ -836,14 +836,14 @@ const ZH_CN: PricingContent = {
features: ['BYOK 自带密钥,支持本地 Coding Agent', '社区支持'],
},
go: {
tagline: '轻量需求,轻松交付 · 零配置即用',
tagline: '轻量需求,轻松交付',
ctaLabel: '订阅 Go',
allowance: '8 个热门模型 · 充裕额度',
features: ['完整设计与 Coding 能力', '无需配置供应商 API Key', '额度自动恢复'],
},
plans: {
plus: {
tagline: '独立项目、零散需求,单人交付 · 零配置即用',
tagline: '独立项目、零散需求,单人交付',
ctaLabel: '升级 Plus',
concurrency: '2 个任务并发',
features: [
@@ -854,7 +854,7 @@ const ZH_CN: PricingContent = {
],
},
pro: {
tagline: '一个人产出整个设计团队的活 · 零配置即用',
tagline: '一个人产出整个设计团队的活',
ctaLabel: '升级 Pro',
concurrency: '5 个任务并发',
features: [
@@ -865,7 +865,7 @@ const ZH_CN: PricingContent = {
],
},
max: {
tagline: '把外包设计费砸到零头 · 零配置即用',
tagline: '把外包设计费砸到零头',
ctaLabel: '升级 Max',
concurrency: '10 个任务并发',
features: [
@@ -918,14 +918,14 @@ const ZH_TW: PricingContent = {
features: ['BYOK 自帶密鑰,支援本機 Coding Agent', '社群支援'],
},
go: {
tagline: '輕量需求,輕鬆交付 · 零配置即用',
tagline: '輕量需求,輕鬆交付',
ctaLabel: '訂閱 Go',
allowance: '8 個熱門模型 · 充裕額度',
features: ['完整設計與 Coding 能力', '無需配置供應商 API Key', '額度自動恢復'],
},
plans: {
plus: {
tagline: '獨立專案、零散需求,單人交付 · 零配置即用',
tagline: '獨立專案、零散需求,單人交付',
ctaLabel: '升級 Plus',
concurrency: '2 個任務並行',
features: [
@@ -936,7 +936,7 @@ const ZH_TW: PricingContent = {
],
},
pro: {
tagline: '一個人產出整個設計團隊的活 · 零配置即用',
tagline: '一個人產出整個設計團隊的活',
ctaLabel: '升級 Pro',
concurrency: '5 個任務並行',
features: [
@@ -947,7 +947,7 @@ const ZH_TW: PricingContent = {
],
},
max: {
tagline: '把外包設計費砍到零頭 · 零配置即用',
tagline: '把外包設計費砍到零頭',
ctaLabel: '升級 Max',
concurrency: '10 個任務並行',
features: [
+2 -2
View File
@@ -254,9 +254,9 @@ const cobeEnhancerUrl = `/enhancers/cobe.js?v=${
left: 0;
right: 0;
min-height: var(--home-campaign-banner-height);
border-bottom: 1px solid color-mix(in srgb, var(--coral) 36%, var(--line));
border-bottom: 1px solid color-mix(in srgb, #78ea57 36%, var(--line));
color: var(--ink);
background: color-mix(in srgb, var(--coral) 11%, #fff);
background: #d8ffb5;
font-family: var(--sans);
font-size: 13px;
line-height: 1.25;
@@ -185,6 +185,7 @@ const jsonLd = [
<article
class="info-page pr-page"
data-pricing-root
data-pricing-locale={locale}
data-audience="creator"
data-interval="yearly"
data-cloud-console-environment="production"
@@ -1425,6 +1426,9 @@ const jsonLd = [
line-height: 1.6;
color: var(--ink-soft, #666);
}
.pr-page[data-pricing-locale='en'] .pr-foot {
font-size: calc(10.5px + 2pt);
}
/* ---- Team / Enterprise plans ---- */
.pr-team-panel {
@@ -45,6 +45,7 @@ test('homepage mounts Go ahead of the existing paid-user DeepSeek banner', () =>
assert.match(home, /import GoBanner from ['"]\.\.\/_components\/go-banner\.astro['"]/);
assert.match(home, /<GoBanner locale=\{locale\} \/>[\s\S]*data-home-campaign-banner/);
assert.match(banner, /html\.go-banner-active \.home-campaign-banner/);
assert.match(banner, /\.go-banner\s*\{[^}]*background:\s*#d8ffb5;/s);
});
test('Go banner keeps its sticky height in flow without double-offsetting the mobile hero', () => {
@@ -27,6 +27,7 @@ test('home campaign banner keeps only the arrow visible while preserving an acce
assert.match(source, /data-home-campaign-countdown/);
assert.match(source, /data-campaign-window-label/);
assert.doesNotMatch(source, /距开始/);
assert.match(source, /\.home-campaign-banner\s*\{[^}]*background:\s*#d8ffb5;/s);
assert.match(source, /background:\s*#68f22e/);
assert.match(source, /home-campaign-banner__cta/);
assert.match(source, /<span class="home-campaign-banner__cta" aria-hidden="true">→<\/span>/);
@@ -176,6 +176,8 @@ describe("pricing contract", () => {
it("matches the demo's individual taglines and compact billing copy", async () => {
const content = getPricingContent("en");
const zhContent = getPricingContent("zh");
const zhTwContent = getPricingContent("zh-tw");
const individualPlans = await readFile(PRICING_INDIVIDUAL_PATH, "utf8");
assert.deepEqual(
@@ -192,6 +194,17 @@ describe("pricing contract", () => {
"High-volume creation · Consistent output",
],
);
for (const localizedContent of [zhContent, zhTwContent]) {
assert.deepEqual(
[
localizedContent.go.tagline,
localizedContent.plans.plus.tagline,
localizedContent.plans.pro.tagline,
localizedContent.plans.max.tagline,
].some((tagline) => tagline.includes("零配置即用")),
false,
);
}
assert.equal(content.labels.monthlyRenewal, "First-month price");
assert.equal(content.labels.yearlySubline, "Billed {totalUsd}/year");
assert.deepEqual(
@@ -237,7 +250,7 @@ describe("pricing contract", () => {
);
assert.match(
individualPlans,
/\.new-plan-ribbon\s*\{[^}]*background:\s*#d8ffb5;/s,
/\.new-plan-ribbon\s*\{[^}]*background:\s*var\(--pricing-accent\);/s,
);
assert.match(
individualPlans,
@@ -424,31 +437,39 @@ describe("pricing contract", () => {
it("uses the reviewed popular-model order and keeps ample access check-only", async () => {
const individualPlans = await readFile(PRICING_INDIVIDUAL_PATH, "utf8");
const comparisonBlock = individualPlans.match(
/const comparisonPopular = \[([\s\S]*?)\]\.map/,
const displayOrderBlock = individualPlans.match(
/const popularModelDisplayOrder = \[([\s\S]*?)\];/,
)?.[1];
assert.ok(comparisonBlock);
assert.ok(displayOrderBlock);
const reviewedOrder = [
"DeepSeek V4 Flash",
"DeepSeek V4 Pro",
"GLM-5.2",
"Kimi K2.7 Code",
"MiMo V2.5 Pro",
"MiniMax M2.7",
"Kimi K2.6",
"MiMo V2.5 Pro",
"GLM-5.1",
];
assert.deepEqual(
Array.from(comparisonBlock.matchAll(/'([^']+)'/g), (match) => match[1]),
Array.from(displayOrderBlock.matchAll(/'([^']+)'/g), (match) => match[1]),
reviewedOrder,
);
assert.match(individualPlans, /go:\s*\[[\s\S]*?'GLM-5\.1',\s*\],\s*plus:/);
assert.match(individualPlans, /plus:\s*\['DeepSeek V4 Flash', 'DeepSeek V4 Pro', 'GLM-5\.2', 'Kimi K2\.7 Code'\]/);
assert.match(individualPlans, /pro:\s*\[[\s\S]*?'MiniMax M2\.7',\s*\],\s*max:/);
assert.match(individualPlans, /const comparisonPopular = orderedPopularModels\(\);/);
assert.match(individualPlans, /const popular = orderedPopularModels\(\);/);
assert.match(
individualPlans,
/status\.kind === 'more-ample'\s*\?\s*\(\s*<td><span class=\{`model-access-status \$\{status\.kind\}`\} aria-label=\{status\.text\}><i class="status-icon check"><\/i><\/span><\/td>/s,
/const checkOnly = status\.kind === 'more-ample' \|\| status\.kind === 'ample' \|\| status\.kind === 'included';\s*return checkOnly \? \(\s*<td><span class=\{`model-access-status \$\{status\.kind\}`\} aria-label=\{status\.text\}><i class="status-icon check"><\/i><\/span><\/td>/s,
);
assert.doesNotMatch(individualPlans, /\.model-access-status\.(?:ample|included)::after/);
assert.match(
individualPlans,
/\.model-access-status\.more-ample,\s*\.model-access-status\.ample,\s*\.model-access-status\.included\s*\{[^}]*grid-template-columns:\s*1fr;[^}]*place-items:\s*center;/s,
);
assert.match(
individualPlans,
/\.model-access-status\.unavailable::before,\s*\.model-access-status\.unavailable::after\s*\{[^}]*left:\s*50%;/s,
);
assert.match(
individualPlans,
@@ -456,7 +477,7 @@ describe("pricing contract", () => {
);
});
it("matches the legal footnote size to the model allowance note", async () => {
it("makes only the English legal footnote 2pt larger than the model allowance note", async () => {
const [page, individualPlans] = await Promise.all([
readFile(PRICING_PAGE_PATH, "utf8"),
readFile(PRICING_INDIVIDUAL_PATH, "utf8"),
@@ -464,6 +485,11 @@ describe("pricing contract", () => {
assert.match(individualPlans, /\.individual-usage-note p\s*\{[^}]*font-size:\s*10\.5px;/s);
assert.match(page, /\.pr-foot\s*\{[^}]*font-size:\s*10\.5px;/s);
assert.match(page, /data-pricing-locale=\{locale\}/);
assert.match(
page,
/\.pr-page\[data-pricing-locale='en'\] \.pr-foot\s*\{[^}]*font-size:\s*calc\(10\.5px \+ 2pt\);/s,
);
});
it("renders paid flagship headings as one localized phrase", async () => {