Files
ComfyUI_frontend/apps/website/src/components/learning/TutorialDetailDialog.vue
imick-io 003303d8ac feat(website): pricing API copy + tutorial video captions (#12929)
## Summary

- **Pricing copy** — replace "concurrent API jobs" wording with
"workflows via API" on Creator/Pro plans, add a third feature line to
Standard, and add a new "Run Workflows via API" entry to the "What's
Included" section (existing feature11 → feature12).
- **Tutorial captions** — wire per-locale VTT caption tracks into
`TutorialDetailDialog`, so the CC button in `VideoPlayer` is now
functional for the learning tutorials. `LearningTutorial.caption` is
typed as `readonly VideoTrack[]`; `VideoTrack` is now exported from
`VideoPlayer.vue`.
- Tailwind class ordering in `PriceSection.vue` shifted due to the
formatter on touched lines.

## Test plan

- [ ] `/pricing` page (EN + zh-CN): Standard plan shows the new "1
workflow via API" line; Creator/Pro show "3/5 workflows via API";
"What's Included" lists "Run Workflows via API" above "Parallel job
execution".
- [ ] Open a learning tutorial → CC button is visible in the player →
toggling CC shows English captions in sync with playback.
- [ ] Tutorials without a `caption` entry hide the CC button.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-06-18 20:51:40 +00:00

82 lines
2.5 KiB
Vue

<script setup lang="ts">
import { onMounted, onUnmounted, useTemplateRef } from 'vue'
import type { LearningTutorial } from '../../data/learningTutorials'
import type { Locale } from '../../i18n/translations'
import { lockScroll, unlockScroll } from '../../composables/scrollLock'
import { t } from '../../i18n/translations'
import VideoPlayer from '../common/VideoPlayer.vue'
const { tutorial, locale = 'en' } = defineProps<{
tutorial: LearningTutorial
locale?: Locale
}>()
const emit = defineEmits<{ close: [] }>()
const dialogRef = useTemplateRef<HTMLDialogElement>('dialogRef')
function handleBackdropClick(e: MouseEvent) {
if (e.target === e.currentTarget) emit('close')
}
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') emit('close')
}
onMounted(() => {
lockScroll()
dialogRef.value?.showModal()
})
onUnmounted(() => {
unlockScroll()
})
</script>
<template>
<Teleport to="body">
<dialog
ref="dialogRef"
:aria-label="tutorial.title[locale]"
class="fixed inset-0 z-50 flex size-full max-h-none max-w-none flex-col items-center justify-center border-0 bg-transparent px-4 py-8 backdrop-blur-xl backdrop:bg-transparent lg:px-20 lg:py-8"
@click="handleBackdropClick"
@keydown="handleKeydown"
@close="emit('close')"
>
<button
:aria-label="t('gallery.detail.close', locale)"
class="border-primary-comfy-yellow hover:bg-primary-comfy-yellow group absolute top-8 right-10 z-10 flex size-10 cursor-pointer items-center justify-center rounded-2xl border-2 bg-primary-comfy-ink transition-colors lg:right-26"
@click="emit('close')"
>
<span
class="bg-primary-comfy-yellow size-5 transition-colors group-hover:bg-primary-comfy-ink"
style="mask: url('/icons/close.svg') center / contain no-repeat"
/>
</button>
<div
class="border-primary-comfy-yellow rounded-5xl flex w-full max-w-7xl items-center justify-center overflow-hidden border-2 bg-primary-comfy-ink p-3 lg:p-4"
>
<VideoPlayer
:key="tutorial.id"
:locale
:src="tutorial.videoSrc"
:poster="tutorial.poster"
:tracks="tutorial.caption"
autoplay
class="w-full"
/>
</div>
<h2
class="mt-6 text-center text-lg font-medium text-primary-comfy-canvas lg:text-xl"
>
{{ t('learning.tutorials.titlePrefix', locale) }}
{{ tutorial.title[locale] }}
</h2>
</dialog>
</Teleport>
</template>