mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
feat: extract productCardsSection:
This commit is contained in:
74
apps/website/src/components/common/ProductCardsSection.vue
Normal file
74
apps/website/src/components/common/ProductCardsSection.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import type { Locale, TranslationKey } from '../../i18n/translations'
|
||||
|
||||
import { getRoutes } from '../../config/routes'
|
||||
import { t } from '../../i18n/translations'
|
||||
import ProductCard from './ProductCard.vue'
|
||||
|
||||
type Product = 'local' | 'cloud' | 'api' | 'enterprise'
|
||||
|
||||
const {
|
||||
locale = 'en',
|
||||
excludeProduct,
|
||||
labelKey = 'products.label'
|
||||
} = defineProps<{
|
||||
locale?: Locale
|
||||
excludeProduct?: Product
|
||||
labelKey?: TranslationKey
|
||||
}>()
|
||||
|
||||
const routes = getRoutes(locale)
|
||||
|
||||
const allCards: (ReturnType<typeof cardDef> & { product: Product })[] = [
|
||||
cardDef('local', routes.download, 'bg-primary-warm-gray'),
|
||||
cardDef('cloud', routes.cloud, 'bg-secondary-mauve'),
|
||||
cardDef('api', routes.api, 'bg-primary-comfy-plum'),
|
||||
cardDef('enterprise', routes.cloudEnterprise, 'bg-illustration-forest')
|
||||
]
|
||||
|
||||
function cardDef(product: Product, href: string, bg: string) {
|
||||
return {
|
||||
product,
|
||||
title: t(`products.${product}.title`, locale),
|
||||
description: t(`products.${product}.description`, locale),
|
||||
cta: t(`products.${product}.cta`, locale),
|
||||
href,
|
||||
bg
|
||||
}
|
||||
}
|
||||
|
||||
const cards = excludeProduct
|
||||
? allCards.filter((c) => c.product !== excludeProduct)
|
||||
: allCards
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-primary-comfy-ink px-4 py-20 lg:px-20 lg:py-24">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col items-center text-center">
|
||||
<p
|
||||
class="text-primary-comfy-yellow text-xs font-bold tracking-widest uppercase"
|
||||
>
|
||||
{{ t(labelKey, locale) }}
|
||||
</p>
|
||||
<h2
|
||||
class="text-primary-comfy-canvas mt-4 text-4xl font-light whitespace-pre-line lg:text-5xl"
|
||||
>
|
||||
{{ t('products.heading', locale) }}
|
||||
</h2>
|
||||
<p class="text-primary-comfy-canvas/70 mt-4 text-sm">
|
||||
{{ t('products.subheading', locale) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Cards -->
|
||||
<div
|
||||
:class="[
|
||||
'mt-16 grid grid-cols-1 gap-4',
|
||||
cards.length === 4 ? 'lg:grid-cols-4' : 'lg:grid-cols-3'
|
||||
]"
|
||||
>
|
||||
<ProductCard v-for="card in cards" :key="card.product" v-bind="card" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -1,67 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { Locale } from '../../i18n/translations'
|
||||
|
||||
import { getRoutes } from '../../config/routes'
|
||||
import { t } from '../../i18n/translations'
|
||||
import ProductCard from '../common/ProductCard.vue'
|
||||
import ProductCardsSection from '../common/ProductCardsSection.vue'
|
||||
|
||||
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
|
||||
const routes = getRoutes(locale)
|
||||
|
||||
const cards = [
|
||||
{
|
||||
title: t('products.local.title', locale),
|
||||
description: t('products.local.description', locale),
|
||||
cta: t('products.local.cta', locale),
|
||||
href: routes.download,
|
||||
bg: 'bg-primary-warm-gray'
|
||||
},
|
||||
{
|
||||
title: t('products.cloud.title', locale),
|
||||
description: t('products.cloud.description', locale),
|
||||
cta: t('products.cloud.cta', locale),
|
||||
href: routes.cloud,
|
||||
bg: 'bg-secondary-mauve'
|
||||
},
|
||||
{
|
||||
title: t('products.api.title', locale),
|
||||
description: t('products.api.description', locale),
|
||||
cta: t('products.api.cta', locale),
|
||||
href: routes.api,
|
||||
bg: 'bg-primary-comfy-plum'
|
||||
},
|
||||
{
|
||||
title: t('products.enterprise.title', locale),
|
||||
description: t('products.enterprise.description', locale),
|
||||
cta: t('products.enterprise.cta', locale),
|
||||
href: routes.cloudEnterprise,
|
||||
bg: 'bg-illustration-forest'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-primary-comfy-ink px-4 py-20 lg:px-20 lg:py-24">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col items-center text-center">
|
||||
<p
|
||||
class="text-primary-comfy-yellow text-xs font-bold tracking-widest uppercase"
|
||||
>
|
||||
{{ t('products.label', locale) }}
|
||||
</p>
|
||||
<h2
|
||||
class="text-primary-comfy-canvas mt-4 text-4xl font-light whitespace-pre-line lg:text-5xl"
|
||||
>
|
||||
{{ t('products.heading', locale) }}
|
||||
</h2>
|
||||
<p class="text-primary-warm-gray mt-4 text-sm">
|
||||
{{ t('products.subheading', locale) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Cards -->
|
||||
<div class="mt-16 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<ProductCard v-for="card in cards" :key="card.title" v-bind="card" />
|
||||
</div>
|
||||
</section>
|
||||
<ProductCardsSection :locale="locale" />
|
||||
</template>
|
||||
|
||||
@@ -10,14 +10,14 @@ const { downloadUrl } = useDownloadUrl()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="px-4 py-24 md:px-20 md:py-40">
|
||||
<section class="px-4 py-24 lg:px-20 lg:py-40">
|
||||
<div
|
||||
class="flex flex-col-reverse items-stretch gap-10 md:flex-row md:gap-16"
|
||||
class="flex flex-col-reverse items-stretch gap-10 lg:flex-row lg:gap-16"
|
||||
>
|
||||
<!-- Text content -->
|
||||
<div class="flex flex-1 flex-col justify-between">
|
||||
<div>
|
||||
<h2 class="text-primary-comfy-canvas text-3xl font-light md:text-4xl">
|
||||
<h2 class="text-primary-comfy-canvas text-3xl font-light lg:text-4xl">
|
||||
{{ t('download.ecosystem.heading', locale) }}
|
||||
</h2>
|
||||
<p class="text-primary-comfy-canvas/70 mt-6 text-sm">
|
||||
@@ -26,7 +26,7 @@ const { downloadUrl } = useDownloadUrl()
|
||||
</div>
|
||||
|
||||
<!-- CTA buttons -->
|
||||
<div class="mt-10 flex flex-col gap-4 md:flex-row">
|
||||
<div class="mt-10 flex flex-col gap-4 lg:flex-row">
|
||||
<a
|
||||
:href="downloadUrl"
|
||||
target="_blank"
|
||||
|
||||
@@ -11,31 +11,31 @@ const { downloadUrl } = useDownloadUrl()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="overflow-hidden px-4 pt-20 pb-16 md:px-20 md:py-24">
|
||||
<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 />
|
||||
|
||||
<h1
|
||||
class="text-primary-comfy-canvas mt-8 max-w-[10ch] text-5xl/tight font-light whitespace-pre-line md:max-w-none md:text-5xl"
|
||||
class="text-primary-comfy-canvas mt-8 max-w-[10ch] text-5xl/tight font-light whitespace-pre-line lg:max-w-none lg:text-5xl"
|
||||
>
|
||||
{{ t('download.hero.heading', locale) }}
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="text-primary-comfy-canvas mt-8 max-w-xs text-sm md:mt-20 md:max-w-xl md:text-base"
|
||||
class="text-primary-comfy-canvas mt-8 max-w-xs text-sm lg:mt-20 lg:max-w-xl lg:text-base"
|
||||
>
|
||||
{{ t('download.hero.subtitle', locale) }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="mt-10 flex w-full max-w-md flex-col gap-4 md:w-auto md:max-w-none md:flex-row"
|
||||
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="downloadUrl"
|
||||
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 md:min-w-60"
|
||||
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('download.hero.downloadLocal', locale) }}
|
||||
</a>
|
||||
@@ -43,7 +43,7 @@ const { downloadUrl } = useDownloadUrl()
|
||||
:href="externalLinks.github"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="border-primary-comfy-yellow text-primary-comfy-yellow hover:bg-primary-comfy-yellow hover:text-primary-comfy-ink flex items-center justify-center gap-2 rounded-full border px-8 py-4 text-sm font-bold tracking-wider transition-colors md:min-w-60"
|
||||
class="border-primary-comfy-yellow text-primary-comfy-yellow hover:bg-primary-comfy-yellow hover:text-primary-comfy-ink flex items-center justify-center gap-2 rounded-full border px-8 py-4 text-sm font-bold tracking-wider transition-colors lg:min-w-60"
|
||||
>
|
||||
<span
|
||||
class="icon-mask size-5 mask-[url('/icons/social/github.svg')]"
|
||||
@@ -56,7 +56,7 @@ const { downloadUrl } = useDownloadUrl()
|
||||
|
||||
<!-- Placeholder for future animation; clipped within hero section -->
|
||||
<div
|
||||
class="relative mt-4 flex h-104 w-full max-w-4xl items-start justify-center overflow-hidden md:mt-12 md:h-136 md:items-center"
|
||||
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"
|
||||
>
|
||||
<div
|
||||
class="border-secondary-mauve/35 bg-primary-comfy-ink/35 absolute top-20 left-2 h-64 w-44 rotate-30 rounded-4xl border"
|
||||
@@ -68,7 +68,7 @@ const { downloadUrl } = useDownloadUrl()
|
||||
class="border-secondary-mauve/35 bg-primary-comfy-ink/35 absolute top-52 right-4 h-56 w-40 rotate-30 rounded-4xl border"
|
||||
/>
|
||||
|
||||
<div class="relative z-10 mt-28 grid grid-cols-3 gap-0.5 md:mt-0">
|
||||
<div class="relative z-10 mt-28 grid grid-cols-3 gap-0.5 lg:mt-0">
|
||||
<span class="bg-secondary-mauve block size-9 rounded-lg" />
|
||||
<span class="bg-primary-comfy-plum block size-9 rounded-lg" />
|
||||
<span class="bg-secondary-mauve block size-9 rounded-lg" />
|
||||
|
||||
@@ -1,60 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { Locale } from '../../../i18n/translations'
|
||||
|
||||
import { getRoutes } from '../../../config/routes'
|
||||
import { t } from '../../../i18n/translations'
|
||||
import ProductCard from '../../common/ProductCard.vue'
|
||||
import ProductCardsSection from '../../common/ProductCardsSection.vue'
|
||||
|
||||
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
|
||||
const routes = getRoutes(locale)
|
||||
|
||||
const cards = [
|
||||
{
|
||||
title: t('products.cloud.title', locale),
|
||||
description: t('products.cloud.description', locale),
|
||||
cta: t('products.cloud.cta', locale),
|
||||
href: routes.cloud,
|
||||
bg: 'bg-secondary-mauve'
|
||||
},
|
||||
{
|
||||
title: t('products.api.title', locale),
|
||||
description: t('products.api.description', locale),
|
||||
cta: t('products.api.cta', locale),
|
||||
href: routes.api,
|
||||
bg: 'bg-primary-comfy-plum'
|
||||
},
|
||||
{
|
||||
title: t('products.enterprise.title', locale),
|
||||
description: t('products.enterprise.description', locale),
|
||||
cta: t('products.enterprise.cta', locale),
|
||||
href: routes.cloudEnterprise,
|
||||
bg: 'bg-illustration-forest'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-primary-comfy-ink px-4 py-24 md:px-20 md:py-40">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col items-center text-center">
|
||||
<p
|
||||
class="text-primary-comfy-yellow text-xs font-bold tracking-widest uppercase"
|
||||
>
|
||||
{{ t('products.label', locale) }}
|
||||
</p>
|
||||
<h2
|
||||
class="text-primary-comfy-canvas mt-4 text-4xl font-light whitespace-pre-line md:text-5xl"
|
||||
>
|
||||
{{ t('products.heading', locale) }}
|
||||
</h2>
|
||||
<p class="text-primary-comfy-canvas/70 mt-4 text-sm">
|
||||
{{ t('products.subheading', locale) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Cards -->
|
||||
<div class="mt-16 grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<ProductCard v-for="card in cards" :key="card.title" v-bind="card" />
|
||||
</div>
|
||||
</section>
|
||||
<ProductCardsSection
|
||||
:locale="locale"
|
||||
exclude-product="local"
|
||||
label-key="products.labelProducts"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -134,6 +134,10 @@ const translations = {
|
||||
en: 'Comfy UI',
|
||||
'zh-CN': 'Comfy UI'
|
||||
},
|
||||
'products.labelProducts': {
|
||||
en: 'Products',
|
||||
'zh-CN': '产品'
|
||||
},
|
||||
'products.heading': {
|
||||
en: 'The AI creation\nengine for complete control',
|
||||
'zh-CN': '完全掌控的\nAI 创作引擎'
|
||||
|
||||
Reference in New Issue
Block a user