mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-06-08 15:29:52 +00:00
## Summary Adds a new Learning page to the website with a hero, featured workflow showcase, tutorials section, and CTA, wired into site nav and footer Resources. ## Changes - **What**: - New `/learning` page (Astro) with `HeroSection`, `FeaturedWorkflowSection`, `TutorialsSection`, and `CallToActionSection` - Localized for `zh-CN` at `/zh-CN/learning` - Featured workflow CTA links out to `comfy.org/workflows/<slug>` - Added `nav.learning` translation; added Learning entry to `SiteNav` and `SiteFooter` Resources - New shared `PillButton`, `MaskRevealButton`, `Badge`, and `VideoPlayer` work used by the page; `TutorialDetailDialog` for tutorial deep-dives - Featured demo video updated; poster image added - `routes.ts`: added `learning` route entry - `EventsSection` temporarily hidden pending content ## Review Focus - Copy on `learning.featured.description` (newly written, both `en` and `zh-CN`) - Tutorial data shape in `data/learningTutorials*.ts` - Internal-vs-external link styling: Learning shows the active-page yellow when viewing `/learning` (expected — internal route, no external arrow) ## Screenshots (if applicable) _Add deployment preview screenshots here._ --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Alexander Brown <drjkl@comfy.org>
121 lines
3.8 KiB
Vue
121 lines
3.8 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
|
|
import type { Locale } from '../../i18n/translations'
|
|
|
|
import {
|
|
getTutorialPosterSrc,
|
|
learningTutorials
|
|
} from '../../data/learningTutorials'
|
|
import { t } from '../../i18n/translations'
|
|
import Badge from '../common/Badge.vue'
|
|
import MaskRevealButton from '../common/MaskRevealButton.vue'
|
|
import TutorialDetailDialog from './TutorialDetailDialog.vue'
|
|
|
|
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
|
|
|
|
const activeTutorialId = ref<string | null>(null)
|
|
const activeTutorial = () =>
|
|
learningTutorials.find((tutorial) => tutorial.id === activeTutorialId.value)
|
|
</script>
|
|
|
|
<template>
|
|
<section class="max-w-9xl mx-auto px-6 py-16 lg:py-24">
|
|
<h2
|
|
class="text-primary-comfy-canvas mb-12 text-4xl font-light tracking-tight lg:mb-16 lg:text-6xl"
|
|
>
|
|
{{ t('learning.tutorials.heading', locale) }}
|
|
</h2>
|
|
|
|
<ul
|
|
class="grid grid-cols-1 gap-x-6 gap-y-10 md:grid-cols-2 lg:grid-cols-3 lg:gap-x-8"
|
|
>
|
|
<li
|
|
v-for="tutorial in learningTutorials"
|
|
:key="tutorial.id"
|
|
class="bg-transparency-white-t4 flex flex-col gap-4 overflow-hidden rounded-3xl border-0 p-2"
|
|
>
|
|
<button
|
|
type="button"
|
|
class="group relative block aspect-video cursor-pointer overflow-hidden rounded-3xl"
|
|
:aria-label="`${t('learning.tutorials.titlePrefix', locale)} ${tutorial.title[locale]}`"
|
|
@click="activeTutorialId = tutorial.id"
|
|
>
|
|
<video
|
|
:src="getTutorialPosterSrc(tutorial)"
|
|
:poster="tutorial.poster"
|
|
class="size-full object-cover"
|
|
preload="metadata"
|
|
playsinline
|
|
muted
|
|
></video>
|
|
<span
|
|
class="absolute inset-0 flex items-center justify-center"
|
|
aria-hidden="true"
|
|
>
|
|
<span
|
|
class="flex size-14 items-center justify-center rounded-full bg-white/25 backdrop-blur-sm transition-transform group-hover:scale-105 lg:size-16"
|
|
>
|
|
<svg
|
|
class="ml-1 size-5 text-white lg:size-6"
|
|
viewBox="0 0 24 24"
|
|
fill="currentColor"
|
|
aria-hidden="true"
|
|
>
|
|
<path d="M8 5v14l11-7z" />
|
|
</svg>
|
|
</span>
|
|
</span>
|
|
</button>
|
|
|
|
<div class="flex flex-col space-y-3 p-4">
|
|
<div class="flex items-center justify-between gap-4">
|
|
<h3
|
|
class="text-primary-comfy-canvas text-sm/snug lg:text-base/snug"
|
|
>
|
|
{{ t('learning.tutorials.titlePrefix', locale) }}<wbr />
|
|
{{ tutorial.title[locale] }}
|
|
</h3>
|
|
<MaskRevealButton
|
|
v-if="tutorial.href"
|
|
:href="tutorial.href"
|
|
icon-position="right"
|
|
class="shrink-0"
|
|
variant="ghost"
|
|
size="sm"
|
|
>
|
|
{{ t('cta.tryWorkflow', locale) }}
|
|
<template #icon>
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="3"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
class="size-4"
|
|
>
|
|
<polyline points="9 6 15 12 9 18" />
|
|
</svg>
|
|
</template>
|
|
</MaskRevealButton>
|
|
</div>
|
|
|
|
<ul class="flex flex-wrap gap-2">
|
|
<li v-for="tag in tutorial.tags" :key="tag">
|
|
<Badge>{{ t(tag, locale) }}</Badge>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
|
|
<TutorialDetailDialog
|
|
v-if="activeTutorial()"
|
|
:tutorial="activeTutorial()!"
|
|
:locale="locale"
|
|
@close="activeTutorialId = null"
|
|
/>
|
|
</section>
|
|
</template>
|