Skip to content

Commit 2a0a7ef

Browse files
feat(website): announce Wan Animate 2 at /wan-animate-2 (#14849)
## Summary Adds the Wan Animate 2 announcement page from Figma `10396-41358`. **Stacked on #14822** (base is `website/seedance-coming-soon`, not main), because it needs the overlay hero layout that PR introduces. Retarget to main once #14822 lands. ## Changes - **What**: New `/wan-animate-2` (en + zh-CN) rendered by the existing model-launch template, plus a footer link under Products. - Pure data on the announcement shape /seedance-2.5 established: one config, its `wanAnimate2.*` keys, two five-line page stubs, one line in the registry test. **No new components.** - Shares the backdrop with the other announcement pages rather than uploading a copy: the art is byte identical in all three Figma frames, so it lives once at `media.comfy.org/website/coming-soon/hero.webp`. ## Review Focus The design and the Seedance frame disagreed on the hero treatment: this frame is newer and uses a transparent CTA with a white border over a 50% scrim, where Seedance used a filled pill over 33%. Rather than ship two styles a week apart, #14822 moves all three onto this newer treatment, so that change lives there and this PR inherits it. Two defects in the design were corrected rather than copied: the CTA reads "RUN COMY FOR FREE" (missing F), and the breadcrumb still reads "SEEDANCE 2.0" from the frame it was duplicated off. Gates: `astro check` 0 errors, unused-i18n clean, knip/format clean, build 585 pages, `validate:jsonld` 588, e2e 8/8 (English, zh-CN and mobile). ## Preview - https://comfy-website-preview-pr-14849.vercel.app/wan-animate-2 - zh-CN: https://comfy-website-preview-pr-14849.vercel.app/zh-CN/wan-animate-2
1 parent 73fab2e commit 2a0a7ef

8 files changed

Lines changed: 283 additions & 2 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { expect } from '@playwright/test'
2+
3+
import { externalLinks, getRoutes } from '../src/config/routes'
4+
import { wanAnimate2Page } from '../src/data/wanAnimate2'
5+
import { t } from '../src/i18n/translations'
6+
import { test } from './fixtures/blockExternalMedia'
7+
8+
const PATH = '/wan-animate-2'
9+
const ZH_PATH = '/zh-CN/wan-animate-2'
10+
const HERO_TITLE = t('wanAnimate2.hero.title')
11+
const HERO_EYEBROW = t('wanAnimate2.hero.eyebrow')
12+
const HERO_CTA = t('wanAnimate2.hero.primaryCta')
13+
const RUN_OPTIONS_HEADING = t('wanAnimate2.runOptions.heading')
14+
const REVIEWS_HEADING = t('wanAnimate2.reviews.heading')
15+
const MODELS_ROUTE = getRoutes('en').models
16+
17+
test.describe('Wan Animate 2 announcement page @smoke', () => {
18+
test.beforeEach(async ({ page }) => {
19+
await page.goto(PATH)
20+
})
21+
22+
test('renders the hero over its backdrop and is indexable', async ({
23+
page
24+
}) => {
25+
const hero = page.locator('section').filter({
26+
has: page.getByRole('heading', { level: 1, name: HERO_TITLE })
27+
})
28+
await expect(hero.getByText(HERO_EYEBROW)).toBeVisible()
29+
await expect(
30+
page.getByRole('heading', { level: 1, name: HERO_TITLE })
31+
).toBeVisible()
32+
await expect(hero.locator('img')).toHaveAttribute(
33+
'src',
34+
wanAnimate2Page.hero.placeholderImageSrc ?? ''
35+
)
36+
await expect(page.locator('meta[name="robots"]')).toHaveCount(0)
37+
})
38+
39+
test('hero CTA sends visitors to the cloud in a new tab', async ({
40+
page
41+
}) => {
42+
const cta = page
43+
.locator('section')
44+
.filter({
45+
has: page.getByRole('heading', { level: 1, name: HERO_TITLE })
46+
})
47+
.getByRole('link', { name: HERO_CTA })
48+
await expect(cta).toHaveAttribute('href', externalLinks.cloud)
49+
await expect(cta).toHaveAttribute('target', '_blank')
50+
})
51+
52+
test('breadcrumb trail links to the models catalog', async ({ page }) => {
53+
const modelsCrumb = page
54+
.getByRole('navigation', { name: t('ui.breadcrumb') })
55+
.getByRole('link', { name: t('models.breadcrumb.models') })
56+
await expect(modelsCrumb).toHaveAttribute('href', MODELS_ROUTE)
57+
})
58+
59+
test('footer links back to this page', async ({ page }) => {
60+
const footerLink = page
61+
.locator('footer')
62+
.getByRole('link', { name: t('footer.wanAnimate2') })
63+
await expect(footerLink).toHaveAttribute(
64+
'href',
65+
getRoutes('en').wanAnimate2
66+
)
67+
})
68+
69+
test('renders run options and reviews', async ({ page }) => {
70+
const runOptions = page.getByRole('heading', {
71+
level: 2,
72+
name: RUN_OPTIONS_HEADING
73+
})
74+
await runOptions.scrollIntoViewIfNeeded()
75+
await expect(runOptions).toBeVisible()
76+
77+
const reviews = page.getByRole('heading', {
78+
level: 2,
79+
name: REVIEWS_HEADING
80+
})
81+
await reviews.scrollIntoViewIfNeeded()
82+
await expect(reviews).toBeVisible()
83+
})
84+
85+
// The announcement omits gallery, pricing, FAQ and closing CTA, so these two
86+
// are the only sections it should show. Asserting the whole set means
87+
// reintroducing any of them has to be deliberate.
88+
test('shows only the sections the announcement configures', async ({
89+
page
90+
}) => {
91+
await expect(page.getByRole('heading', { level: 2 })).toHaveText([
92+
RUN_OPTIONS_HEADING,
93+
REVIEWS_HEADING
94+
])
95+
await expect(page.locator('video')).toHaveCount(0)
96+
})
97+
})
98+
99+
test.describe('Wan Animate 2 announcement page — zh-CN', () => {
100+
test.beforeEach(async ({ page }) => {
101+
await page.goto(ZH_PATH)
102+
})
103+
104+
test('renders the localized hero and run options', async ({ page }) => {
105+
// Resolve the heading in zh-CN rather than reusing HERO_TITLE. The two are
106+
// the same string today because the model name is not translated, but
107+
// scoping by locale here is what the test actually means.
108+
const hero = page.locator('section').filter({
109+
has: page.getByRole('heading', {
110+
level: 1,
111+
name: t('wanAnimate2.hero.title', 'zh-CN')
112+
})
113+
})
114+
await expect(
115+
hero.getByText(t('wanAnimate2.hero.eyebrow', 'zh-CN'))
116+
).toBeVisible()
117+
await expect(hero.getByRole('link')).toContainText(/[-鿿]/)
118+
119+
await expect(
120+
page.getByRole('heading', {
121+
level: 2,
122+
name: t('wanAnimate2.runOptions.heading', 'zh-CN')
123+
})
124+
).toBeVisible()
125+
})
126+
127+
test('breadcrumb and footer keep visitors inside the locale', async ({
128+
page
129+
}) => {
130+
const modelsCrumb = page
131+
.getByRole('navigation', { name: t('ui.breadcrumb', 'zh-CN') })
132+
.getByRole('link', { name: t('models.breadcrumb.models', 'zh-CN') })
133+
await expect(modelsCrumb).toHaveAttribute('href', getRoutes('zh-CN').models)
134+
135+
const footerLink = page
136+
.locator('footer')
137+
.getByRole('link', { name: t('footer.wanAnimate2', 'zh-CN') })
138+
await expect(footerLink).toHaveAttribute(
139+
'href',
140+
getRoutes('zh-CN').wanAnimate2
141+
)
142+
})
143+
})
144+
145+
test.describe('Wan Animate 2 announcement page — mobile @mobile', () => {
146+
test('hero CTA stays visible and linked at narrow viewports', async ({
147+
page
148+
}) => {
149+
await page.goto(PATH)
150+
151+
const cta = page
152+
.locator('section')
153+
.filter({
154+
has: page.getByRole('heading', { level: 1, name: HERO_TITLE })
155+
})
156+
.getByRole('link', { name: HERO_CTA })
157+
158+
await expect(cta).toBeVisible()
159+
await expect(cta).toHaveAttribute('href', externalLinks.cloud)
160+
161+
const box = await cta.boundingBox()
162+
const viewport = page.viewportSize()
163+
if (!box || !viewport) {
164+
throw new Error('hero CTA has no layout box to measure')
165+
}
166+
167+
// Measured without scrolling first: the point is that the CTA is reachable
168+
// on load, so scrolling it into view would make the vertical bounds pass no
169+
// matter how far down the page it sat. One pixel of tolerance for subpixel
170+
// rounding.
171+
expect(box.x).toBeGreaterThanOrEqual(-1)
172+
expect(box.y).toBeGreaterThanOrEqual(-1)
173+
expect(box.x + box.width).toBeLessThanOrEqual(viewport.width + 1)
174+
expect(box.y + box.height).toBeLessThanOrEqual(viewport.height + 1)
175+
})
176+
})

apps/website/src/components/common/SiteFooter.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ const topColumns: { title: string; links: FooterLink[] }[] = [
4141
{ label: t('nav.mcpServer', locale), href: routes.mcp },
4242
{ label: t('nav.supportedModels', locale), href: routes.models },
4343
{ label: t('footer.minimaxH3', locale), href: routes.minimax },
44-
{ label: t('footer.seedance', locale), href: routes.seedance }
44+
{ label: t('footer.seedance', locale), href: routes.seedance },
45+
{ label: t('footer.wanAnimate2', locale), href: routes.wanAnimate2 }
4546
]
4647
},
4748
{

apps/website/src/config/routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const baseRoutes = {
2626
minimax: '/minimax',
2727
flux3: '/flux-3',
2828
seedance: '/seedance-2.5',
29+
wanAnimate2: '/wan-animate-2',
2930
brand: '/brand'
3031
} as const
3132

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { ModelLaunchPage } from '../templates/model-launch/types'
2+
3+
import { externalLinks } from '../config/routes'
4+
import { COMING_SOON_HERO } from './comingSoonHero'
5+
6+
// Wan Animate 2 has not shipped yet, so this page announces it and holds the URL.
7+
// On launch day, replace this config with the full one the way /flux-3 did; the
8+
// page stubs and the template stay as they are.
9+
export const wanAnimate2Page: ModelLaunchPage = {
10+
metaTitleKey: 'wanAnimate2.meta.title',
11+
metaDescriptionKey: 'wanAnimate2.meta.description',
12+
breadcrumbLabelKey: 'wanAnimate2.breadcrumb.model',
13+
breadcrumbUpdatedKey: 'wanAnimate2.breadcrumb.updated',
14+
hero: {
15+
layout: 'overlay',
16+
placeholderImageSrc: COMING_SOON_HERO,
17+
eyebrowKey: 'wanAnimate2.hero.eyebrow',
18+
titleKey: 'wanAnimate2.hero.title',
19+
primaryCta: {
20+
labelKey: 'wanAnimate2.hero.primaryCta',
21+
href: externalLinks.cloud,
22+
target: '_blank'
23+
}
24+
},
25+
runOptions: {
26+
headingKey: 'wanAnimate2.runOptions.heading',
27+
subtitleKey: 'wanAnimate2.runOptions.subtitle',
28+
ctaKey: 'wanAnimate2.runOptions.cta'
29+
},
30+
reviews: {
31+
headingKey: 'wanAnimate2.reviews.heading',
32+
highlight: {
33+
titleKey: 'wanAnimate2.reviews.highlightTitle',
34+
descriptionKey: 'wanAnimate2.reviews.highlightDescription',
35+
ctaKey: 'wanAnimate2.reviews.highlightCta'
36+
}
37+
}
38+
}

apps/website/src/i18n/translations.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5162,6 +5162,57 @@ const translations = {
51625162
'你的 AI 助手可以接入整个生态、构建工作流,并生成图像、视频、音频或 3D 内容。'
51635163
},
51645164
'seedance.reviews.highlightCta': { en: 'GET STARTED', 'zh-CN': '开始使用' },
5165+
// Wan Animate 2 model page (/wan-animate-2) — announcement until the model ships
5166+
'wanAnimate2.meta.title': {
5167+
en: 'Wan Animate 2 on Comfy — Coming Soon',
5168+
'zh-CN': 'Comfy 上的 Wan Animate 2 — 即将推出'
5169+
},
5170+
'wanAnimate2.meta.description': {
5171+
en: 'Wan Animate 2 is coming to Comfy. Run it on Comfy Cloud the day it lands, or start building workflows for free now with every other model Comfy supports.',
5172+
'zh-CN':
5173+
'Wan Animate 2 即将登陆 Comfy。上线当天即可在 Comfy Cloud 上运行;现在就可以用 Comfy 支持的其他模型免费开始搭建工作流。'
5174+
},
5175+
'wanAnimate2.breadcrumb.model': {
5176+
en: 'Wan Animate 2',
5177+
'zh-CN': 'Wan Animate 2'
5178+
},
5179+
'wanAnimate2.breadcrumb.updated': {
5180+
en: 'Updated August 2026',
5181+
'zh-CN': '更新于 2026 年 8 月'
5182+
},
5183+
'wanAnimate2.hero.eyebrow': { en: 'Coming soon', 'zh-CN': '即将推出' },
5184+
'wanAnimate2.hero.title': { en: 'Wan Animate 2', 'zh-CN': 'Wan Animate 2' },
5185+
'wanAnimate2.hero.primaryCta': {
5186+
en: 'RUN COMFY FOR FREE',
5187+
'zh-CN': '免费使用 Comfy'
5188+
},
5189+
'wanAnimate2.runOptions.heading': {
5190+
en: 'One engine, every way to run it',
5191+
'zh-CN': '同一引擎,多种运行方式'
5192+
},
5193+
'wanAnimate2.runOptions.subtitle': {
5194+
en: 'Build workflows in the browser today. Batch campaigns with the API, or bring it in-house.',
5195+
'zh-CN': '今天就在浏览器中搭建工作流。用 API 批量制作,或部署到自有环境。'
5196+
},
5197+
'wanAnimate2.runOptions.cta': { en: 'LEARN MORE', 'zh-CN': '了解更多' },
5198+
'wanAnimate2.reviews.heading': {
5199+
en: '4+ million Comfy creators say',
5200+
'zh-CN': '超过 400 万 Comfy 创作者这样说'
5201+
},
5202+
'wanAnimate2.reviews.highlightTitle': {
5203+
en: 'Comfy MCP: now turn your agent into a creative technologist.',
5204+
'zh-CN': 'Comfy MCP:让你的智能体成为创意技术专家。'
5205+
},
5206+
'wanAnimate2.reviews.highlightDescription': {
5207+
en: 'Your AI assistant can access the ecosystem, build workflows, and generate images, video, audio, or 3D.',
5208+
'zh-CN':
5209+
'你的 AI 助手可以接入整个生态、构建工作流,并生成图像、视频、音频或 3D 内容。'
5210+
},
5211+
'wanAnimate2.reviews.highlightCta': {
5212+
en: 'GET STARTED',
5213+
'zh-CN': '开始使用'
5214+
},
5215+
'footer.wanAnimate2': { en: 'Wan Animate 2', 'zh-CN': 'Wan Animate 2' },
51655216
'minimax.meta.title': {
51665217
en: 'MiniMax H3 on Comfy — Open-Weight Video Model',
51675218
'zh-CN': 'Comfy 上的 MiniMax H3 — 开源权重视频模型'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
import ModelLaunchPage from '../templates/model-launch/ModelLaunchPage.astro'
3+
import { wanAnimate2Page } from '../data/wanAnimate2'
4+
---
5+
6+
<ModelLaunchPage page={wanAnimate2Page} />
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
import ModelLaunchPage from '../../templates/model-launch/ModelLaunchPage.astro'
3+
import { wanAnimate2Page } from '../../data/wanAnimate2'
4+
---
5+
6+
<ModelLaunchPage page={wanAnimate2Page} />

apps/website/src/templates/model-launch/modelLaunchPages.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'
33
import { flux3Page } from '../../data/flux3'
44
import { minimaxPage } from '../../data/minimax'
55
import { seedancePage } from '../../data/seedance'
6+
import { wanAnimate2Page } from '../../data/wanAnimate2'
67
import type { TranslationKey } from '../../i18n/translations'
78
import { t } from '../../i18n/translations'
89
import type { ModelLaunchPage } from './types'
@@ -11,7 +12,8 @@ import type { ModelLaunchPage } from './types'
1112
const pages: { name: string; page: ModelLaunchPage }[] = [
1213
{ name: 'minimax', page: minimaxPage },
1314
{ name: 'flux3', page: flux3Page },
14-
{ name: 'seedance', page: seedancePage }
15+
{ name: 'seedance', page: seedancePage },
16+
{ name: 'wanAnimate2', page: wanAnimate2Page }
1517
]
1618

1719
describe.for(pages)('$name launch page config', ({ page }) => {

0 commit comments

Comments
 (0)