feat: website api and enterprise page

This commit is contained in:
Yourz
2026-04-16 00:34:55 +08:00
parent 97c50a30a7
commit e10dfb98eb
24 changed files with 1198 additions and 9 deletions

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import type { Locale } from '../../../i18n/translations'
import { t } from '../../../i18n/translations'
import FeatureShowcaseSection from '../shared/FeatureShowcaseSection.vue'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
const features = [
{
title: t('api.automation.feature1.title', locale),
description: t('api.automation.feature1.description', locale),
description2: t('api.automation.feature1.description2', locale)
},
{
title: t('api.automation.feature2.title', locale),
description: t('api.automation.feature2.description', locale),
description2: t('api.automation.feature2.description2', locale)
},
{
title: t('api.automation.feature3.title', locale),
description: t('api.automation.feature3.description', locale)
}
]
</script>
<template>
<FeatureShowcaseSection
:heading="t('api.automation.heading', locale)"
:subtitle="t('api.automation.subtitle', locale)"
:features="features"
/>
</template>

View File

@@ -0,0 +1,124 @@
<script setup lang="ts">
import type { Locale } from '../../../i18n/translations'
import { externalLinks } from '../../../config/routes'
import { t } from '../../../i18n/translations'
import ProductHeroBadge from '../../common/ProductHeroBadge.vue'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
</script>
<template>
<section class="overflow-hidden px-4 pt-20 pb-16 lg:px-20 lg:py-24">
<div class="mx-auto flex max-w-5xl flex-col items-center">
<div class="flex w-full max-w-2xl flex-col items-center text-center">
<ProductHeroBadge text="API" />
<h1
class="text-primary-comfy-canvas mt-8 text-5xl/tight font-light whitespace-pre-line lg:text-5xl"
>
{{ t('api.hero.heading', locale) }}
</h1>
<p
class="text-primary-comfy-canvas mt-8 max-w-xs text-sm lg:mt-20 lg:max-w-xl lg:text-base"
>
{{ t('api.hero.subtitle', locale) }}
</p>
<div
class="mt-10 flex w-full max-w-md flex-col gap-4 lg:w-auto lg:max-w-none lg:flex-row"
>
<a
:href="externalLinks.app"
target="_blank"
rel="noopener"
class="bg-primary-comfy-yellow text-primary-comfy-ink rounded-full px-8 py-4 text-center text-sm font-bold tracking-wider transition-opacity hover:opacity-90 lg:min-w-60"
>
{{ t('api.hero.getApiKeys', locale) }}
</a>
<a
:href="externalLinks.docs"
target="_blank"
rel="noopener"
class="border-primary-comfy-yellow text-primary-comfy-yellow hover:bg-primary-comfy-yellow hover:text-primary-comfy-ink rounded-full border px-8 py-4 text-center text-sm font-bold tracking-wider transition-colors lg:min-w-60"
>
{{ t('api.hero.viewDocs', locale) }}
</a>
</div>
</div>
<!-- Isometric node illustration -->
<div
class="relative mt-4 flex h-104 w-full max-w-4xl items-start justify-center overflow-hidden lg:mt-12 lg:h-136 lg:items-center"
>
<!-- Background layers -->
<div
class="border-secondary-mauve/20 absolute bottom-8 h-40 w-72 rounded-3xl border lg:bottom-16 lg:h-48 lg:w-96"
/>
<div
class="border-secondary-mauve/20 absolute bottom-16 h-40 w-60 rounded-3xl border lg:bottom-28 lg:h-48 lg:w-80"
/>
<div
class="border-secondary-mauve/20 absolute bottom-24 h-40 w-48 rounded-3xl border lg:bottom-40 lg:h-48 lg:w-64"
/>
<!-- Isometric grid of nodes -->
<div
class="relative z-10 mt-28 grid grid-cols-5 gap-1 lg:mt-0 lg:gap-1.5"
style="transform: rotateX(55deg) rotateZ(45deg)"
>
<span
class="bg-primary-comfy-yellow block size-8 rounded-lg lg:size-10"
/>
<span class="bg-secondary-mauve block size-8 rounded-lg lg:size-10" />
<span
class="bg-primary-comfy-plum block size-8 rounded-lg lg:size-10"
/>
<span class="bg-secondary-mauve block size-8 rounded-lg lg:size-10" />
<span
class="bg-primary-comfy-yellow block size-8 rounded-lg lg:size-10"
/>
<span class="bg-secondary-mauve block size-8 rounded-lg lg:size-10" />
<span
class="bg-primary-comfy-plum block size-8 rounded-lg lg:size-10"
/>
<span
class="bg-primary-comfy-yellow block size-8 rounded-lg lg:size-10"
/>
<span
class="bg-primary-comfy-plum block size-8 rounded-lg lg:size-10"
/>
<span class="bg-secondary-mauve block size-8 rounded-lg lg:size-10" />
<span
class="bg-primary-comfy-plum block size-8 rounded-lg lg:size-10"
/>
<span class="bg-secondary-mauve block size-8 rounded-lg lg:size-10" />
<span
class="bg-primary-comfy-plum block size-8 rounded-lg lg:size-10"
/>
<span class="bg-secondary-mauve block size-8 rounded-lg lg:size-10" />
<span
class="bg-primary-comfy-plum block size-8 rounded-lg lg:size-10"
/>
<span
class="bg-primary-comfy-yellow block size-8 rounded-lg lg:size-10"
/>
<span
class="bg-primary-comfy-plum block size-8 rounded-lg lg:size-10"
/>
<span class="bg-secondary-mauve block size-8 rounded-lg lg:size-10" />
<span
class="bg-primary-comfy-yellow block size-8 rounded-lg lg:size-10"
/>
<span
class="bg-primary-comfy-plum block size-8 rounded-lg lg:size-10"
/>
</div>
</div>
</div>
</section>
</template>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import type { Locale } from '../../../i18n/translations'
import { t } from '../../../i18n/translations'
import SharedReasonSection from '../shared/ReasonSection.vue'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
const reasons = [
{
title: t('api.reason.1.title', locale),
description: t('api.reason.1.description', locale)
},
{
title: t('api.reason.2.title', locale),
description: t('api.reason.2.description', locale)
},
{
title: t('api.reason.3.title', locale),
description: t('api.reason.3.description', locale)
},
{
title: t('api.reason.4.title', locale),
description: t('api.reason.4.description', locale)
}
]
</script>
<template>
<SharedReasonSection
:heading="t('api.reason.heading', locale)"
:heading-highlight="t('api.reason.headingHighlight', locale)"
:heading-suffix="t('api.reason.headingSuffix', locale)"
:subtitle="t('api.reason.subtitle', locale)"
:reasons="reasons"
/>
</template>

View File

@@ -0,0 +1,93 @@
<script setup lang="ts">
import type { Locale } from '../../../i18n/translations'
import { externalLinks } from '../../../config/routes'
import { t } from '../../../i18n/translations'
import CardGridSection from '../shared/CardGridSection.vue'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
const steps = [
{
number: '01',
titleKey: 'api.steps.step1.title' as const,
descriptionKey: 'api.steps.step1.description' as const
},
{
number: '02',
titleKey: 'api.steps.step2.title' as const,
descriptionKey: 'api.steps.step2.description' as const
},
{
number: '03',
titleKey: 'api.steps.step3.title' as const,
descriptionKey: 'api.steps.step3.description' as const
}
]
</script>
<template>
<CardGridSection :heading="t('api.steps.heading', locale)" :columns="3">
<div
v-for="step in steps"
:key="step.number"
class="bg-primary-comfy-ink flex aspect-square flex-col rounded-3xl border border-white/10 p-6"
>
<!-- Isometric illustration area -->
<div class="flex flex-1 items-center justify-center" aria-hidden="true">
<div
class="grid grid-cols-5 gap-0.5"
style="transform: rotateX(55deg) rotateZ(45deg)"
>
<span
v-for="i in 15"
:key="i"
class="block size-5 rounded-sm"
:class="[
i === 3 || i === 8 || i === 13
? 'bg-primary-comfy-yellow'
: i % 3 === 0
? 'bg-primary-comfy-plum'
: 'bg-secondary-mauve'
]"
/>
</div>
</div>
<!-- Step content -->
<p class="text-primary-comfy-yellow text-sm font-bold tracking-wider">
{{ step.number }}
</p>
<h3 class="text-primary-comfy-canvas mt-2 text-xl font-semibold">
{{ t(step.titleKey, locale) }}
</h3>
<p class="mt-3 text-sm text-smoke-700">
{{ t(step.descriptionKey, locale) }}
</p>
</div>
<!-- CTA buttons -->
<template #footer>
<div
class="mt-12 flex flex-col items-center gap-4 lg:flex-row lg:justify-center"
>
<a
:href="externalLinks.app"
target="_blank"
rel="noopener"
class="bg-primary-comfy-yellow text-primary-comfy-ink w-full rounded-full px-8 py-4 text-center text-sm font-bold tracking-wider transition-opacity hover:opacity-90 lg:w-auto lg:min-w-48"
>
{{ t('api.hero.getApiKeys', locale) }}
</a>
<a
:href="externalLinks.docs"
target="_blank"
rel="noopener"
class="border-primary-comfy-yellow text-primary-comfy-yellow hover:bg-primary-comfy-yellow hover:text-primary-comfy-ink w-full rounded-full border px-8 py-4 text-center text-sm font-bold tracking-wider transition-colors lg:w-auto lg:min-w-48"
>
{{ t('api.hero.viewDocs', locale) }}
</a>
</div>
</template>
</CardGridSection>
</template>

View File

@@ -0,0 +1,62 @@
<script setup lang="ts">
import type { Locale } from '../../../i18n/translations'
import { t } from '../../../i18n/translations'
import CardGridSection from '../shared/CardGridSection.vue'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
const cards = [
{
titleKey: 'enterprise.byoKey.card1.title' as const,
descriptionKey: 'enterprise.byoKey.card1.description' as const
},
{
titleKey: 'enterprise.byoKey.card2.title' as const,
descriptionKey: 'enterprise.byoKey.card2.description' as const
}
]
</script>
<template>
<CardGridSection
:heading="t('enterprise.byoKey.heading', locale)"
:subtitle="t('enterprise.byoKey.subtitle', locale)"
:columns="2"
>
<div
v-for="(card, i) in cards"
:key="i"
class="bg-primary-comfy-ink flex aspect-square flex-col rounded-3xl border border-white/10 p-6"
>
<!-- Isometric illustration area -->
<div class="flex flex-1 items-center justify-center" aria-hidden="true">
<div
class="grid grid-cols-5 gap-0.5"
style="transform: rotateX(55deg) rotateZ(45deg)"
>
<span
v-for="j in 15"
:key="j"
class="block size-5 rounded-sm"
:class="[
j === 3 || j === 8 || j === 13
? 'bg-primary-comfy-yellow'
: j % 3 === 0
? 'bg-primary-comfy-plum'
: 'bg-secondary-mauve'
]"
/>
</div>
</div>
<!-- Card content -->
<h3 class="text-primary-comfy-canvas mt-2 text-xl font-semibold">
{{ t(card.titleKey, locale) }}
</h3>
<p class="mt-3 text-sm text-smoke-700">
{{ t(card.descriptionKey, locale) }}
</p>
</div>
</CardGridSection>
</template>

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import type { Locale } from '../../../i18n/translations'
import { t } from '../../../i18n/translations'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
</script>
<template>
<section class="relative overflow-hidden px-4 py-24 lg:px-20 lg:py-32">
<!-- Decorative images -->
<!-- Top-left -->
<div
class="absolute top-0 -left-8 size-36 rounded-3xl bg-smoke-200 lg:left-[10%] lg:size-64"
role="img"
aria-hidden="true"
/>
<!-- Top-right -->
<div
class="absolute top-0 -right-8 size-28 rounded-3xl bg-smoke-200 lg:top-4 lg:right-[2%] lg:size-40"
role="img"
aria-hidden="true"
/>
<!-- Middle-left -->
<div
class="absolute top-1/3 -left-12 size-40 rounded-3xl bg-smoke-200 lg:top-[35%] lg:left-[2%] lg:h-56 lg:w-48"
role="img"
aria-hidden="true"
/>
<!-- Middle-right -->
<div
class="absolute top-[45%] -right-4 hidden h-48 w-72 rounded-3xl bg-smoke-200 lg:block"
role="img"
aria-hidden="true"
/>
<!-- Bottom-right -->
<div
class="absolute -right-4 bottom-8 hidden size-48 rounded-3xl bg-smoke-200 lg:block"
role="img"
aria-hidden="true"
/>
<!-- Text content -->
<div
class="relative z-10 mx-auto flex max-w-3xl flex-col items-center py-16 text-center lg:py-24"
>
<h2 class="text-primary-comfy-canvas text-5xl font-light lg:text-8xl">
{{ t('enterprise.ownership.line1', locale) }}
</h2>
<h2
class="text-primary-comfy-canvas mt-6 text-5xl font-light lg:mt-10 lg:text-8xl"
>
{{ t('enterprise.ownership.line2', locale) }}
</h2>
<h2
class="text-primary-comfy-canvas mt-6 text-5xl font-light lg:mt-10 lg:text-8xl"
>
{{ t('enterprise.ownership.line3', locale) }}
</h2>
<p
class="text-primary-comfy-canvas mt-12 max-w-xl text-sm lg:mt-16 lg:text-base"
>
{{ t('enterprise.ownership.subtitle', locale) }}
</p>
</div>
</section>
</template>

View File

@@ -0,0 +1,122 @@
<script setup lang="ts">
import type { Locale } from '../../../i18n/translations'
import { t } from '../../../i18n/translations'
import ProductHeroBadge from '../../common/ProductHeroBadge.vue'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
</script>
<template>
<section class="overflow-hidden px-4 pt-20 pb-16 lg:px-20 lg:py-24">
<div class="mx-auto flex max-w-5xl flex-col items-center">
<div class="flex w-full max-w-2xl flex-col items-center text-center">
<ProductHeroBadge text="ENTERPRISE" />
<h1
class="text-primary-comfy-canvas mt-8 text-5xl/tight font-light whitespace-pre-line lg:text-5xl"
>
{{ t('enterprise.hero.heading', locale) }}
</h1>
<p
class="text-primary-comfy-canvas mt-8 max-w-xs text-sm lg:mt-20 lg:max-w-xl lg:text-base"
>
{{ t('enterprise.hero.subtitle', locale) }}
</p>
<div
class="mt-10 flex w-full max-w-md flex-col lg:w-auto lg:max-w-none"
>
<a
href="/contact"
class="bg-primary-comfy-yellow text-primary-comfy-ink rounded-full px-8 py-4 text-center text-sm font-bold tracking-wider transition-opacity hover:opacity-90 lg:min-w-60"
>
{{ t('enterprise.hero.contactSales', locale) }}
</a>
</div>
</div>
<!-- Illustration -->
<div
class="relative mt-4 flex h-104 w-full max-w-4xl items-start justify-center overflow-hidden lg:mt-12 lg:h-136 lg:items-center"
>
<!-- Background hexagonal outlines -->
<div
class="border-secondary-mauve/20 absolute top-12 size-64 rounded-3xl border lg:top-8 lg:size-80"
/>
<div
class="border-secondary-mauve/20 absolute top-20 size-56 rounded-3xl border lg:top-16 lg:size-72"
/>
<!-- Isometric node clusters -->
<div class="relative z-10 mt-20 flex gap-4 lg:mt-0 lg:gap-6">
<div
class="grid grid-cols-3 gap-0.5"
style="transform: rotateX(55deg) rotateZ(45deg)"
>
<span
class="bg-secondary-mauve block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-comfy-plum block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-comfy-yellow block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-comfy-yellow block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-warm-gray block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-secondary-mauve block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-comfy-plum block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-comfy-yellow block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-secondary-mauve block size-7 rounded-lg lg:size-9"
/>
</div>
<div
class="grid grid-cols-3 gap-0.5"
style="transform: rotateX(55deg) rotateZ(45deg)"
>
<span
class="bg-primary-warm-gray block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-comfy-yellow block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-secondary-mauve block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-secondary-mauve block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-comfy-plum block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-warm-gray block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-comfy-yellow block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-secondary-mauve block size-7 rounded-lg lg:size-9"
/>
<span
class="bg-primary-comfy-plum block size-7 rounded-lg lg:size-9"
/>
</div>
</div>
</div>
</div>
</section>
</template>

View File

@@ -0,0 +1,61 @@
<script setup lang="ts">
import type { Locale } from '../../../i18n/translations'
import { t } from '../../../i18n/translations'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
</script>
<template>
<section class="px-4 py-24 lg:px-20">
<div
class="bg-transparency-white-t4 rounded-5xl flex flex-col gap-8 p-2 lg:flex-row lg:items-stretch lg:gap-16"
>
<!-- Image -->
<div class="lg:w-1/2">
<div
class="aspect-square w-full rounded-4xl bg-smoke-200"
role="img"
:aria-label="t('enterprise.orchestration.heading', locale)"
/>
</div>
<!-- Text -->
<div class="flex flex-col justify-between p-6 lg:w-1/2">
<div>
<h2
class="text-primary-comfy-canvas text-3xl font-light lg:text-5xl/tight"
>
{{ t('enterprise.orchestration.heading', locale) }}
</h2>
<p
class="text-primary-comfy-yellow mt-8 text-lg font-semibold lg:mt-10 lg:text-2xl"
>
{{ t('enterprise.orchestration.highlight', locale) }}
</p>
<p class="mt-6 text-sm text-smoke-700 lg:text-base">
{{ t('enterprise.orchestration.description', locale) }}
</p>
<p class="mt-4 text-sm text-smoke-500 italic lg:text-base">
{{ t('enterprise.orchestration.quote', locale) }}
</p>
</div>
<div class="mt-10 lg:mt-0">
<a
href="/contact"
class="bg-primary-comfy-yellow text-primary-comfy-ink inline-block rounded-full px-8 py-4 text-sm font-bold tracking-wider transition-opacity hover:opacity-90"
>
{{ t('enterprise.hero.contactSales', locale) }}
</a>
<p class="mt-4 text-xs text-smoke-500 lg:text-sm">
{{ t('enterprise.orchestration.footer', locale) }}
</p>
</div>
</div>
</div>
</section>
</template>

View File

@@ -0,0 +1,39 @@
<script setup lang="ts">
import type { Locale } from '../../../i18n/translations'
import { t } from '../../../i18n/translations'
import SharedReasonSection from '../shared/ReasonSection.vue'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
const reasons = [
{
title: t('enterprise.reason.1.title', locale),
description: t('enterprise.reason.1.description', locale)
},
{
title: t('enterprise.reason.2.title', locale),
description: t('enterprise.reason.2.description', locale)
},
{
title: t('enterprise.reason.3.title', locale),
description: t('enterprise.reason.3.description', locale)
},
{
title: t('enterprise.reason.4.title', locale),
description: t('enterprise.reason.4.description', locale)
},
{
title: t('enterprise.reason.5.title', locale),
description: t('enterprise.reason.5.description', locale)
}
]
</script>
<template>
<SharedReasonSection
:heading="t('enterprise.reason.heading', locale)"
:subtitle="t('enterprise.reason.subtitle', locale)"
:reasons="reasons"
/>
</template>

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import type { Locale } from '../../../i18n/translations'
import { getRoutes } from '../../../config/routes'
import { t } from '../../../i18n/translations'
import FeatureShowcaseSection from '../shared/FeatureShowcaseSection.vue'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
const routes = getRoutes(locale)
const features = [
{
title: t('enterprise.team.feature1.title', locale),
description: t('enterprise.team.feature1.description', locale)
},
{
title: t('enterprise.team.feature2.title', locale),
description: t('enterprise.team.feature2.description', locale),
ctaText: t('enterprise.team.feature2.cta', locale),
ctaHref: routes.cloud
},
{
title: t('enterprise.team.feature3.title', locale),
description: t('enterprise.team.feature3.description', locale),
ctaText: t('enterprise.hero.contactSales', locale),
ctaHref: '/contact'
}
]
</script>
<template>
<FeatureShowcaseSection
:heading="t('enterprise.team.heading', locale)"
:subtitle="t('enterprise.team.subtitle', locale)"
:features="features"
/>
</template>

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
defineProps<{
heading: string
subtitle?: string
columns?: 2 | 3
}>()
</script>
<template>
<section class="px-4 py-24 lg:px-20">
<div class="mx-auto max-w-3xl text-center">
<h2
class="text-primary-comfy-canvas text-3xl font-light lg:text-5xl/tight"
>
{{ heading }}
</h2>
<p v-if="subtitle" class="mt-4 text-sm text-smoke-700 lg:text-base">
{{ subtitle }}
</p>
</div>
<div
class="mt-12 grid gap-6 lg:mt-16"
:class="columns === 2 ? 'lg:grid-cols-2' : 'lg:grid-cols-3'"
>
<slot />
</div>
<slot name="footer" />
</section>
</template>

View File

@@ -0,0 +1,87 @@
<script setup lang="ts">
import { cn } from '@comfyorg/tailwind-utils'
interface Feature {
title: string
description: string
description2?: string
ctaText?: string
ctaHref?: string
}
defineProps<{
heading: string
subtitle: string
features: Feature[]
}>()
</script>
<template>
<section class="px-4 py-24 lg:px-20">
<!-- Section header -->
<div class="mx-auto max-w-3xl text-center">
<h2
class="text-primary-comfy-canvas text-3xl font-light lg:text-5xl/tight"
>
{{ heading }}
</h2>
<p class="mt-4 text-sm text-smoke-700 lg:text-base">
{{ subtitle }}
</p>
</div>
<!-- Features -->
<div class="mt-24 flex flex-col gap-4 lg:gap-8">
<div
v-for="(feature, i) in features"
:key="i"
class="bg-transparency-white-t4 rounded-5xl flex flex-col gap-8 p-2 lg:flex-row lg:items-stretch lg:gap-12"
>
<!-- Text -->
<div
:class="
cn(
'order-2 flex flex-col p-6 lg:w-1/2 lg:justify-between',
i % 2 === 0 ? 'lg:order-1' : 'lg:order-2'
)
"
>
<h3 class="text-primary-comfy-canvas text-2xl font-light lg:text-3xl">
{{ feature.title }}
</h3>
<div class="mt-6 lg:mt-0">
<p class="text-sm text-smoke-700 lg:text-base">
{{ feature.description }}
</p>
<p
v-if="feature.description2"
class="mt-4 text-sm text-smoke-700 lg:text-base"
>
{{ feature.description2 }}
</p>
<a
v-if="feature.ctaText && feature.ctaHref"
:href="feature.ctaHref"
class="bg-primary-comfy-yellow text-primary-comfy-ink mt-6 inline-block rounded-full px-6 py-3 text-xs font-bold tracking-wider transition-opacity hover:opacity-90"
>
{{ feature.ctaText }}
</a>
</div>
</div>
<!-- Image -->
<div
:class="
cn('order-1 lg:w-1/2', i % 2 === 0 ? 'lg:order-2' : 'lg:order-1')
"
>
<div
class="aspect-4/3 w-full rounded-4xl bg-smoke-200"
role="img"
:aria-label="feature.title"
/>
</div>
</div>
</div>
</section>
</template>

View File

@@ -7,10 +7,14 @@ interface Reason {
const {
heading,
headingHighlight = '',
headingSuffix = '',
subtitle = '',
reasons
} = defineProps<{
heading: string
headingHighlight?: string
headingSuffix?: string
subtitle?: string
reasons: Reason[]
}>()
</script>
@@ -29,8 +33,12 @@ const {
{{ heading
}}<span v-if="headingHighlight" class="text-primary-warm-white">{{
headingHighlight
}}</span>
}}</span
>{{ headingSuffix }}
</h2>
<p v-if="subtitle" class="text-primary-comfy-canvas/70 mt-6 text-sm">
{{ subtitle }}
</p>
</div>
<!-- Right reasons list -->

View File

@@ -223,6 +223,333 @@ const translations = {
'zh-CN': 'Comfy 为您提供构建模块,创造出前所未有的工作流——并与所有人分享。'
},
// API HeroSection
'api.hero.heading': {
en: 'Turn any workflow into\na production endpoint.',
'zh-CN': '将任何工作流转化为\n生产级端点。'
},
'api.hero.subtitle': {
en: 'Design your workflows. Deploy them as API calls. Automate generation, integrate with your systems, and scale to thousands of outputs on Comfy Cloud.',
'zh-CN':
'设计你的工作流。将它们部署为 API 调用。自动化生成、与你的系统集成,并在 Comfy Cloud 上扩展到数千个输出。'
},
'api.hero.getApiKeys': {
en: 'GET API KEYS',
'zh-CN': '获取 API 密钥'
},
'api.hero.viewDocs': {
en: 'VIEW DOCS',
'zh-CN': '查看文档'
},
// Enterprise TeamSection
'enterprise.team.heading': {
en: 'Team workspaces\nand shared assets.',
'zh-CN': '团队工作区\n与共享资产。'
},
'enterprise.team.subtitle': {
en: 'Organize workflows, models, and outputs in shared workspaces. Control who builds, who runs, and who deploys.',
'zh-CN':
'在共享工作区中组织工作流、模型和输出。控制谁构建、谁运行、谁部署。'
},
'enterprise.team.feature1.title': {
en: 'Role-based access',
'zh-CN': '基于角色的访问控制'
},
'enterprise.team.feature1.description': {
en: 'Control who builds, who runs, and who deploys.',
'zh-CN': '控制谁构建、谁运行、谁部署。'
},
'enterprise.team.feature2.title': {
en: 'Single Sign-On',
'zh-CN': '单点登录'
},
'enterprise.team.feature2.description': {
en: 'Most things you build locally run on Comfy Cloud \u2014 same file, same results, powerful GPUs on demand. When a job outgrows your machine, push it to the cloud. No conversion, no rework.',
'zh-CN':
'你在本地构建的大部分内容都能在 Comfy Cloud 上运行——相同文件、相同结果、按需使用强大 GPU。当任务超出你的机器能力时推送到云端。无需转换无需返工。'
},
'enterprise.team.feature2.cta': {
en: 'SEE CLOUD FEATURES',
'zh-CN': '查看云端特性'
},
'enterprise.team.feature3.title': {
en: 'App Mode',
'zh-CN': 'App 模式'
},
'enterprise.team.feature3.description': {
en: 'Non-technical team members run workflows without touching the node graph.',
'zh-CN': '非技术团队成员无需接触节点图即可运行工作流。'
},
// Enterprise ReasonSection
'enterprise.reason.heading': {
en: 'Enterprise-grade infrastructure for the creative engine inside your organization.',
'zh-CN': '为组织内的创作引擎提供企业级基础设施。'
},
'enterprise.reason.subtitle': {
en: 'Comfy Cloud API gives you a managed infrastructure so you can focus on the workflow, not the hardware.',
'zh-CN':
'Comfy Cloud API 为你提供托管基础设施,让你专注于工作流,而非硬件。'
},
'enterprise.reason.1.title': {
en: 'Dedicated GPU compute',
'zh-CN': '专属 GPU 算力'
},
'enterprise.reason.1.description': {
en: 'Reserved server-grade, powerful compute for your organization.',
'zh-CN': '为你的组织预留的服务器级强大算力。'
},
'enterprise.reason.2.title': {
en: 'Priority queuing',
'zh-CN': '优先队列'
},
'enterprise.reason.2.description': {
en: 'Your production jobs run first.',
'zh-CN': '你的生产任务优先运行。'
},
'enterprise.reason.3.title': {
en: 'Flexible Deployment',
'zh-CN': '灵活部署'
},
'enterprise.reason.3.description': {
en: 'Same workflows on Comfy Cloud. Scale compute up or down based on your production schedule.',
'zh-CN': '在 Comfy Cloud 上运行相同的工作流。根据生产计划弹性扩缩算力。'
},
'enterprise.reason.4.title': {
en: 'Custom SLAs',
'zh-CN': '自定义 SLA'
},
'enterprise.reason.4.description': {
en: 'Uptime and response commitments built around your schedule.',
'zh-CN': '根据你的时间安排定制正常运行时间和响应承诺。'
},
'enterprise.reason.5.title': {
en: 'Commercial license guaranteed',
'zh-CN': '商业许可保障'
},
'enterprise.reason.5.description': {
en: 'Every model available through Comfy Cloud is cleared for commercial use. No license ambiguity for your legal team.',
'zh-CN':
'通过 Comfy Cloud 提供的每个模型均已获得商业使用许可。法务团队无需担心许可歧义。'
},
// Enterprise HeroSection
'enterprise.hero.heading': {
en: 'Your team already runs ComfyUI. Scale it with confidence.',
'zh-CN': '你的团队已经在使用 ComfyUI。放心地扩展它。'
},
'enterprise.hero.subtitle': {
en: 'ComfyUI Enterprise adds managed infrastructure, team controls, and dedicated support to the workflows your organization already builds.',
'zh-CN':
'ComfyUI 企业版为你的组织已有的工作流添加托管基础设施、团队控制和专属支持。'
},
'enterprise.hero.contactSales': {
en: 'CONTACT SALES',
'zh-CN': '联系销售'
},
// Enterprise DataOwnershipSection
'enterprise.ownership.line1': {
en: 'Your data.',
'zh-CN': '你的数据。'
},
'enterprise.ownership.line2': {
en: 'Your network.',
'zh-CN': '你的网络。'
},
'enterprise.ownership.line3': {
en: 'Your terms.',
'zh-CN': '你的条款。'
},
'enterprise.ownership.subtitle': {
en: 'Your workflows, models, and generated outputs stay within your organization\u2019s environment. Role-based access controls and data isolation built for organizations with the strictest requirements.',
'zh-CN':
'你的工作流、模型和生成输出始终保留在你的组织环境中。基于角色的访问控制和数据隔离,为最严格要求的组织而构建。'
},
// Enterprise BYOKeySection
'enterprise.byoKey.heading': {
en: 'Bring your own API key',
'zh-CN': '自带 API 密钥'
},
'enterprise.byoKey.subtitle': {
en: 'Use your own contracts with third-party model providers. Comfy orchestrates the pipeline. You choose which models to run and whose API keys to use.',
'zh-CN':
'使用你与第三方模型提供商的合约。Comfy 编排管线。你决定运行哪些模型、使用谁的 API 密钥。'
},
'enterprise.byoKey.card1.title': {
en: 'API key management',
'zh-CN': 'API 密钥管理'
},
'enterprise.byoKey.card1.description': {
en: 'Bring your own API keys from any model provider. Use your existing contracts and pricing.',
'zh-CN': '从任何模型提供商导入你自己的 API 密钥。使用你现有的合约和定价。'
},
'enterprise.byoKey.card2.title': {
en: 'Real-time progress',
'zh-CN': '实时进度'
},
'enterprise.byoKey.card2.description': {
en: 'Step-by-step execution updates via WebSocket.',
'zh-CN': '通过 WebSocket 逐步更新执行状态。'
},
// Enterprise OrchestrationSection
'enterprise.orchestration.heading': {
en: 'The orchestration layer isn\u2019t worth rebuilding.',
'zh-CN': '编排层不值得重建。'
},
'enterprise.orchestration.highlight': {
en: 'Every team that evaluates building this internally reaches the same conclusion.',
'zh-CN': '每个评估过内部自建的团队都得出了同样的结论。'
},
'enterprise.orchestration.description': {
en: 'Internal estimates are 12\u201318 months with a dedicated engineering team. No matter how good or how fast, building this internally won\u2019t have 5,000+ extensions, 60,000+ nodes, or same-day model support.',
'zh-CN':
'内部评估需要一个专属工程团队耗时 12\u201318 个月。无论多优秀多快速,自建方案都不会拥有 5,000+ 扩展、60,000+ 节点或当日模型支持。'
},
'enterprise.orchestration.quote': {
en: 'Platforms like OpenArt and Fal already run Comfy underneath \u2014 because they reached the same conclusion.',
'zh-CN':
'OpenArt 和 Fal 等平台底层已经运行 Comfy——因为他们得出了同样的结论。'
},
'enterprise.orchestration.footer': {
en: 'Comfy Enterprise plans come with support from the team that builds the engine: direct access to our engineering team and a whiteglove onboarding.',
'zh-CN':
'Comfy 企业版计划附带引擎开发团队的支持:直接访问我们的工程团队和白手套式入职服务。'
},
// API StepsSection
'api.steps.heading': {
en: 'Three steps to production',
'zh-CN': '三步进入生产'
},
'api.steps.step1.title': {
en: 'Design the workflow',
'zh-CN': '设计工作流'
},
'api.steps.step1.description': {
en: 'Build and test in the ComfyUI interface \u2014 locally or on Comfy Cloud. When it works, export the API format.',
'zh-CN':
'在 ComfyUI 界面中构建和测试——本地或 Comfy Cloud 上。测试通过后,导出 API 格式。'
},
'api.steps.step2.title': {
en: 'Submit via API',
'zh-CN': '通过 API 提交'
},
'api.steps.step2.description': {
en: 'Send the workflow JSON or a single request to the API endpoint. Modify inputs programmatically \u2014 prompts, seeds, images, any parameter on any node.',
'zh-CN':
'将工作流 JSON 或单个请求发送到 API 端点。以编程方式修改输入——提示词、种子、图像、任何节点上的任何参数。'
},
'api.steps.step3.title': {
en: 'Monitor and Retrieve',
'zh-CN': '监控与获取'
},
'api.steps.step3.description': {
en: 'Track progress in real time over WebSocket. Download the finished output when the job completes.',
'zh-CN': '通过 WebSocket 实时跟踪进度。作业完成后下载最终输出。'
},
// API AutomationSection
'api.automation.heading': {
en: 'The automation layer for\nprofessional-grade visual AI',
'zh-CN': '专业级视觉 AI 的\n自动化层'
},
'api.automation.subtitle': {
en: 'To transform ComfyUI from a powerful tool to reliable infrastructure.',
'zh-CN': '将 ComfyUI 从强大的工具转化为可靠的基础设施。'
},
'api.automation.feature1.title': {
en: "Automate what can't be manual",
'zh-CN': '自动化无法手动完成的工作'
},
'api.automation.feature1.description': {
en: 'Trigger generation when a user uploads a photo. Process an entire product catalog overnight. Swap a character\u2019s expression across 500 frames.',
'zh-CN':
'当用户上传照片时触发生成。一夜之间处理整个产品目录。在 500 帧中替换角色表情。'
},
'api.automation.feature1.description2': {
en: 'The API is for running workflows inside other systems \u2014 triggered by code, not by a person at a keyboard.',
'zh-CN':
'API 用于在其他系统内运行工作流——由代码触发,而非由键盘前的人触发。'
},
'api.automation.feature2.title': {
en: 'Inject dynamic values at runtime',
'zh-CN': '在运行时注入动态值'
},
'api.automation.feature2.description': {
en: 'Pass a different face, a different product, a different brand palette into the same pipeline \u2014 consistent, controlled output every time.',
'zh-CN':
'将不同的面孔、不同的产品、不同的品牌色板传入同一管线——每次都获得一致、可控的输出。'
},
'api.automation.feature2.description2': {
en: 'The workflow stays fixed. The inputs change.',
'zh-CN': '工作流保持不变。输入可以改变。'
},
'api.automation.feature3.title': {
en: 'Integrate ComfyUI into the rest of your stack',
'zh-CN': '将 ComfyUI 集成到你的技术栈中'
},
'api.automation.feature3.description': {
en: 'Generation is usually one step in a larger system. Fetch input from S3, call the ComfyUI API, post-process the result, write it to a database, and notify a team.',
'zh-CN':
'生成通常只是更大系统中的一个步骤。从 S3 获取输入、调用 ComfyUI API、后处理结果、写入数据库、通知团队。'
},
// API ReasonSection
'api.reason.heading': {
en: 'Deploy on\n',
'zh-CN': '部署在\n'
},
'api.reason.headingHighlight': {
en: 'Comfy Cloud —\n',
'zh-CN': 'Comfy Cloud——\n'
},
'api.reason.headingSuffix': {
en: 'no servers to\nmanage.',
'zh-CN': '无需管理\n服务器。'
},
'api.reason.subtitle': {
en: 'Comfy Cloud API gives you a managed infrastructure so you can focus on the workflow, not the hardware.',
'zh-CN':
'Comfy Cloud API 为你提供托管基础设施,让你专注于工作流,而非硬件。'
},
'api.reason.1.title': {
en: 'Powerful GPUs on demand',
'zh-CN': '按需使用强大 GPU'
},
'api.reason.1.description': {
en: 'Heavy video generation, complex multi-step pipelines, large batch jobs.',
'zh-CN': '重度视频生成、复杂多步管线、大批量任务。'
},
'api.reason.2.title': {
en: 'One API key. Immediate access.',
'zh-CN': '一个 API 密钥。即时访问。'
},
'api.reason.2.description': {
en: 'No environment setup, no model downloads, no GPU procurement.',
'zh-CN': '无需环境配置、无需下载模型、无需采购 GPU。'
},
'api.reason.3.title': {
en: 'Open models & partner models through one endpoint',
'zh-CN': '通过一个端点访问开源模型和合作伙伴模型'
},
'api.reason.3.description': {
en: 'Open-source models alongside partner models like Kling, Luma, Nano Banana, Grok, and Runway \u2014 all with one credit balance.',
'zh-CN':
'开源模型与 Kling、Luma、Nano Banana、Grok、Runway 等合作伙伴模型并存——全部使用同一额度。'
},
'api.reason.4.title': {
en: 'Real-time progress',
'zh-CN': '实时进度'
},
'api.reason.4.description': {
en: 'Step-by-step execution updates and live previews during generation via WebSocket.',
'zh-CN': '通过 WebSocket 获取逐步执行更新和生成过程中的实时预览。'
},
// Download FAQSection
'download.faq.heading': {
en: "FAQ's",

View File

@@ -1,8 +1,18 @@
---
import BaseLayout from '../layouts/BaseLayout.astro'
import ComingSoon from '../components/common/ComingSoon.astro'
import CloudBannerSection from '../components/product/shared/CloudBannerSection.vue'
import HeroSection from '../components/product/api/HeroSection.vue'
import AutomationSection from '../components/product/api/AutomationSection.vue'
import StepsSection from '../components/product/api/StepsSection.vue'
import ReasonSection from '../components/product/api/ReasonSection.vue'
import ProductCardsSection from '../components/common/ProductCardsSection.vue'
---
<BaseLayout title="Comfy API">
<ComingSoon />
<CloudBannerSection />
<HeroSection />
<AutomationSection />
<StepsSection />
<ReasonSection />
<ProductCardsSection exclude-product="api" label-key="products.labelProducts" />
</BaseLayout>

View File

@@ -1,8 +1,24 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro'
import ComingSoon from '../../components/common/ComingSoon.astro'
import CloudBannerSection from '../../components/product/shared/CloudBannerSection.vue'
import HeroSection from '../../components/product/enterprise/HeroSection.vue'
import SocialProofBarSection from '../../components/common/SocialProofBarSection.vue'
import ReasonSection from '../../components/product/enterprise/ReasonSection.vue'
import TeamSection from '../../components/product/enterprise/TeamSection.vue'
import DataOwnershipSection from '../../components/product/enterprise/DataOwnershipSection.vue'
import BYOKeySection from '../../components/product/enterprise/BYOKeySection.vue'
import OrchestrationSection from '../../components/product/enterprise/OrchestrationSection.vue'
import ProductCardsSection from '../../components/common/ProductCardsSection.vue'
---
<BaseLayout title="Comfy Enterprise">
<ComingSoon />
<CloudBannerSection />
<HeroSection />
<SocialProofBarSection />
<ReasonSection />
<TeamSection />
<DataOwnershipSection />
<BYOKeySection />
<OrchestrationSection />
<ProductCardsSection exclude-product="enterprise" label-key="products.labelProducts" />
</BaseLayout>

View File

@@ -1,7 +1,7 @@
---
import BaseLayout from '../layouts/BaseLayout.astro'
import HeroSection from '../components/product/local/HeroSection.vue'
import CloudBannerSection from '../components/product/local/CloudBannerSection.vue'
import CloudBannerSection from '../components/product/shared/CloudBannerSection.vue'
import ReasonSection from '../components/product/local/ReasonSection.vue'
import EcoSystemSection from '../components/product/local/EcoSystemSection.vue'
import ProductCardsSection from '../components/product/local/ProductCardsSection.vue'

View File

@@ -2,7 +2,7 @@
import BaseLayout from '../layouts/BaseLayout.astro'
import ProductCardsSection from '../components/home/ProductCardsSection.vue'
import HeroSection from '../components/home/HeroSection.vue'
import SocialProofBarSection from '../components/home/SocialProofBarSection.vue'
import SocialProofBarSection from '../components/common/SocialProofBarSection.vue'
import ProductShowcaseSection from '../components/home/ProductShowcaseSection.vue'
import UseCaseSection from '../components/home/UseCaseSection.vue'
import CaseStudySpotlightSection from '../components/home/CaseStudySpotlightSection.vue'

View File

@@ -0,0 +1,18 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro'
import CloudBannerSection from '../../components/product/shared/CloudBannerSection.vue'
import HeroSection from '../../components/product/api/HeroSection.vue'
import AutomationSection from '../../components/product/api/AutomationSection.vue'
import StepsSection from '../../components/product/api/StepsSection.vue'
import ReasonSection from '../../components/product/api/ReasonSection.vue'
import ProductCardsSection from '../../components/common/ProductCardsSection.vue'
---
<BaseLayout title="Comfy API">
<CloudBannerSection locale="zh-CN" />
<HeroSection locale="zh-CN" />
<AutomationSection locale="zh-CN" />
<StepsSection locale="zh-CN" />
<ReasonSection locale="zh-CN" />
<ProductCardsSection locale="zh-CN" exclude-product="api" label-key="products.labelProducts" />
</BaseLayout>

View File

@@ -0,0 +1,16 @@
---
import BaseLayout from '../../../layouts/BaseLayout.astro'
import CloudBannerSection from '../../../components/product/shared/CloudBannerSection.vue'
import HeroSection from '../../../components/product/enterprise/HeroSection.vue'
import SocialProofBarSection from '../../../components/common/SocialProofBarSection.vue'
import ReasonSection from '../../../components/product/enterprise/ReasonSection.vue'
import TeamSection from '../../../components/product/enterprise/TeamSection.vue'
---
<BaseLayout title="Comfy Enterprise">
<CloudBannerSection locale="zh-CN" />
<HeroSection locale="zh-CN" />
<SocialProofBarSection />
<ReasonSection locale="zh-CN" />
<TeamSection locale="zh-CN" />
</BaseLayout>

View File

@@ -1,6 +1,6 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro'
import CloudBannerSection from '../../components/product/local/CloudBannerSection.vue'
import CloudBannerSection from '../../components/product/shared/CloudBannerSection.vue'
import HeroSection from '../../components/product/local/HeroSection.vue'
import ReasonSection from '../../components/product/local/ReasonSection.vue'
import EcoSystemSection from '../../components/product/local/EcoSystemSection.vue'

View File

@@ -2,7 +2,7 @@
import BaseLayout from '../../layouts/BaseLayout.astro'
import ProductCardsSection from '../../components/home/ProductCardsSection.vue'
import HeroSection from '../../components/home/HeroSection.vue'
import SocialProofBarSection from '../../components/home/SocialProofBarSection.vue'
import SocialProofBarSection from '../../components/common/SocialProofBarSection.vue'
import ProductShowcaseSection from '../../components/home/ProductShowcaseSection.vue'
import UseCaseSection from '../../components/home/UseCaseSection.vue'
import CaseStudySpotlightSection from '../../components/home/CaseStudySpotlightSection.vue'