11<script setup lang="ts">
22import { animate } from ' motion-v'
3+ import { joinURL } from ' ufo'
34
45const { data : page } = await useAsyncData (' figma' , () => queryCollection (' figma' ).first ())
56if (! page .value ) {
67 throw createError ({ statusCode: 404 , statusMessage: ' Page not found' , fatal: true })
78}
89
10+ const { url } = useSiteConfig ()
11+
912useSeoMeta ({
1013 title: page .value .title ,
1114 description: page .value .description ,
1215 ogTitle: page .value .title ,
13- ogDescription: page .value .description
14- })
15-
16- defineOgImageComponent (' Docs' , {
17- headline: ' Community'
16+ ogDescription: page .value .description ,
17+ ogImage: joinURL (url , ' /figma/og-image.png' )
1818})
1919
2020const video = ref <HTMLVideoElement | null >(null )
2121const played = ref (false )
2222
23+ const { width : windowWidth } = useWindowSize ()
24+ const isMobile = computed (() => windowWidth .value < 768 )
25+
2326onMounted (async () => {
24- // Animate cursors
2527 await new Promise (resolve => setTimeout (resolve , 1000 ))
2628 const figmaWordPosition = document .querySelector (' #figma' )?.getBoundingClientRect ()
2729 const nuxtWordPosition = document .querySelector (' #nuxt' )?.getBoundingClientRect ()
2830 const initialScrollX = window .scrollX
2931 const initialScrollY = window .scrollY
3032
3133 if (figmaWordPosition && nuxtWordPosition ) {
32- const cursor1Sequence = async () => {
33- await animate (' #cursor1' , { left: Math .round (Math .random () * window .outerWidth ), top: Math .round (Math .random () * window .outerHeight ) }, { duration: 0.1 }).finished
34- await animate (' #cursor1' , { opacity: 1 }, { duration: 0.3 }).finished
35- await animate (' #cursor1' , {
36- left: Math .round (figmaWordPosition .left + initialScrollX + figmaWordPosition .width / 2 ),
37- top: Math .round (figmaWordPosition .top + initialScrollY - figmaWordPosition .height / 4 )
38- }, { duration: 1.5 , delay: 0.2 , ease: ' easeInOut' }).finished
39- await animate (' #cursor1' , { scale: 0.8 }, { duration: 0.1 , ease: ' easeOut' }).finished
40- await animate (' #cursor1' , { scale: 1 }, { duration: 0.1 , ease: ' easeOut' }).finished
41- await animate (' #figma' , { color: ' var(--ui-info)' }, { duration: 0.3 , ease: ' easeOut' }).finished
42- await animate (' #cursor1' , {
43- left: Math .round (figmaWordPosition .left + initialScrollX + figmaWordPosition .width ),
44- top: Math .round (figmaWordPosition .top + initialScrollY )
45- }, { duration: 0.6 , ease: ' easeInOut' }).finished
46- }
34+ const createCursorSequence = async (
35+ cursorId : string ,
36+ targetWord : DOMRect ,
37+ targetColor : string ,
38+ wordId : string ,
39+ delay : number = 0
40+ ) => {
41+ const maxWidth = isMobile .value ? windowWidth .value * 0.8 : window .outerWidth
42+ const maxHeight = isMobile .value ? window .innerHeight * 0.6 : window .outerHeight
43+
44+ await animate (cursorId , {
45+ left: Math .round (Math .random () * maxWidth ),
46+ top: Math .round (Math .random () * maxHeight )
47+ }, { duration: 0.1 , delay }).finished
48+
49+ await animate (cursorId , { opacity: 1 }, { duration: 0.3 }).finished
4750
48- const cursor2Sequence = async () => {
49- await animate (' #cursor2' , { left: Math .round (Math .random () * window .outerWidth ), top: Math .round (Math .random () * window .outerHeight ) }, { duration: 0.1 , delay: 0.6 }).finished
50- await animate (' #cursor2' , { opacity: 1 }, { duration: 0.3 }).finished
51- await animate (' #cursor2' , {
52- left: Math .round (nuxtWordPosition .left + initialScrollX + nuxtWordPosition .width / 2 ),
53- top: Math .round (nuxtWordPosition .top + initialScrollY - nuxtWordPosition .height / 4 )
54- }, { duration: 1.5 , delay: 0.2 , ease: ' easeInOut' }).finished
55- await animate (' #cursor2' , { scale: 0.8 }, { duration: 0.1 , ease: ' easeOut' }).finished
56- await animate (' #cursor2' , { scale: 1 }, { duration: 0.1 , ease: ' easeOut' }).finished
57- await animate (' #nuxt' , { color: ' var(--ui-success)' }, { duration: 0.3 , ease: ' easeOut' }).finished
58- await animate (' #cursor2' , {
59- left: Math .round (nuxtWordPosition .left + initialScrollX + nuxtWordPosition .width ),
60- top: Math .round (nuxtWordPosition .top + initialScrollY )
61- }, { duration: 0.6 , ease: ' easeInOut' }).finished
51+ const clickPositionX = isMobile .value
52+ ? Math .round (targetWord .left + initialScrollX + targetWord .width * 0.5 )
53+ : Math .round (targetWord .left + initialScrollX + targetWord .width / 2 )
54+ const clickPositionY = isMobile .value
55+ ? Math .round (targetWord .top + initialScrollY - targetWord .height / 0.7 )
56+ : Math .round (targetWord .top + initialScrollY - targetWord .height / 1.2 )
57+
58+ await animate (cursorId , {
59+ left: clickPositionX ,
60+ top: clickPositionY
61+ }, { duration: 1 , delay: 0.2 , ease: ' easeInOut' }).finished
62+
63+ await animate (cursorId , { scale: 0.8 }, { duration: 0.1 , ease: ' easeOut' }).finished
64+ await animate (cursorId , { scale: 1 }, { duration: 0.1 , ease: ' easeOut' }).finished
65+ await animate (wordId , { color: targetColor }, { duration: 0.3 , ease: ' easeOut' }).finished
66+
67+ const finalPositionX = isMobile .value
68+ ? Math .round (targetWord .left + initialScrollX + targetWord .width * 1 )
69+ : Math .round (targetWord .left + initialScrollX + targetWord .width )
70+ const finalPositionY = isMobile .value
71+ ? Math .round (targetWord .top + initialScrollY + targetWord .height * - 1.2 )
72+ : Math .round (targetWord .top + initialScrollY - targetWord .height / 2 )
73+
74+ await animate (cursorId , {
75+ left: finalPositionX ,
76+ top: finalPositionY
77+ }, { duration: 0.5 , ease: ' easeInOut' }).finished
6278 }
6379
64- await Promise .all ([cursor1Sequence (), cursor2Sequence ()])
80+ await Promise .all ([
81+ createCursorSequence (' #cursor1' , figmaWordPosition , ' var(--ui-info)' , ' #figma' ),
82+ createCursorSequence (' #cursor2' , nuxtWordPosition , ' var(--ui-success)' , ' #nuxt' , 0.5 )
83+ ])
6584 }
6685})
6786 </script >
@@ -148,7 +167,8 @@ onMounted(async () => {
148167 :ui =" {
149168 container: 'lg:grid-cols-0 !gap-0 px-4 sm:px-6 lg:px-8',
150169 wrapper: 'grid grid-cols-1 lg:grid-cols-2',
151- description: 'lg:mt-0' }"
170+ description: 'mt-2'
171+ }"
152172 orientation =" horizontal"
153173 class =" rounded-none bg-gradient-to-b from-elevated/50 to-default"
154174 >
@@ -174,20 +194,10 @@ onMounted(async () => {
174194 </UTabs >
175195 </UPageSection >
176196 <UPageSection v-bind =" page.section2" orientation =" horizontal" :ui =" { container: 'py-16 sm:py-16 lg:py-16' }" >
177- <NuxtImg
178- v-if =" page.section2.image"
179- v-bind =" page.section2.image"
180- class =" w-full h-auto rounded-lg"
181- loading =" lazy"
182- />
197+ <NuxtImg v-if =" page.section2.image" v-bind =" page.section2.image" class =" w-full h-auto rounded-lg" loading =" lazy" />
183198 </UPageSection >
184199 <UPageSection v-bind =" page.section3" orientation =" horizontal" :ui =" { container: 'py-16 sm:pt-16 lg:pt-16' }" >
185- <NuxtImg
186- v-if =" page.section3.image"
187- v-bind =" page.section3.image"
188- class =" w-full h-auto rounded-lg"
189- loading =" lazy"
190- />
200+ <NuxtImg v-if =" page.section3.image" v-bind =" page.section3.image" class =" w-full h-auto rounded-lg" loading =" lazy" />
191201 </UPageSection >
192202 <USeparator />
193203 <UPageSection
@@ -207,12 +217,7 @@ onMounted(async () => {
207217 <div aria-hidden =" true" class =" absolute z-[-1] border-x border-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
208218 <ul class =" grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 items-start justify-center border border-default border-b-0 sm:divide-x divide-y lg:divide-y-0 divide-default" >
209219 <li v-for =" (step, index) in page?.section4.steps" :key =" step.title" class =" flex flex-col gap-y-4 justify-start group h-full p-4" >
210- <NuxtImg
211- v-if =" step.image"
212- v-bind =" step.image"
213- class =" rounded-sm"
214- loading =" lazy"
215- />
220+ <NuxtImg v-if =" step.image" v-bind =" step.image" class =" rounded-sm" loading =" lazy" />
216221 <div >
217222 <h2 class =" font-semibold inline-flex items-center gap-x-1" >
218223 <UBadge :label =" index + 1" size =" sm" color =" neutral" variant =" subtle" class =" rounded-full tabular-nums" /> {{ step.title }}
@@ -225,77 +230,15 @@ onMounted(async () => {
225230 </ul >
226231 </UPageSection >
227232 <UPageSection v-bind =" page.features2" :ui =" { container: 'py-16 sm:py-16 lg:py-16', features: 'mt-0' }" class =" border-y border-default" />
228- <UPageSection
229- v-if =" page.pricing"
230- :title =" page.pricing.title"
231- :description =" page.pricing.description"
232- orientation =" vertical"
233- :ui =" {
234- title: 'sm:text-left',
235- description: 'sm:text-left',
236- links: 'sm:justify-start',
237- container: 'relative !pb-0',
238- wrapper: 'sm:pl-8'
239- }"
240- >
241- <div aria-hidden =" true" class =" absolute z-[-1] border-x border-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
242- <UPricingPlans compact class =" -space-x-px" >
243- <UPricingPlan
244- v-for =" (plan, index) in page.pricing.plans"
245- :key =" index"
246- :title =" plan.title"
247- :description =" plan.description"
248- :price =" plan.price"
249- :discount =" plan.discount"
250- :billing-period =" plan.billing_period"
251- :billing-cycle =" plan.billing_cycle"
252- :highlight =" plan.highlight"
253- :features =" plan.features"
254- :button =" plan.button"
255- :terms =" plan.terms"
256- class =" rounded-none"
257- :class =" plan.class"
258- >
259- <template #features >
260- <li v-for =" (feature, i) in plan.features" :key =" i" class =" flex items-center gap-2 min-w-0" >
261- <UIcon name =" i-lucide-circle-check" class =" size-5 shrink-0 text-primary" />
262- <MDC :value =" feature" unwrap =" p" tag =" span" class =" text-sm truncate text-accented" :cache-key =" `figma-pricing-plan-${index}-feature-${i}`" />
263- </li >
264- </template >
265- <template #button >
266- <div class =" flex flex-col w-full items-center gap-2" >
267- <UButton v-bind =" plan.button" block size =" lg" />
268- <UButton
269- v-if =" plan.extraButton"
270- v-bind =" plan.extraButton"
271- block
272- size =" lg"
273- variant =" outline"
274- color =" neutral"
275- />
276- </div >
277- </template >
278- </UPricingPlan >
279- </UPricingPlans >
280- </UPageSection >
233+
281234 <UPageCTA v-if =" page.customers" :title =" page.customers.title" :ui =" { title: '!text-base font-medium', container: 'sm:py-12 sm:gap-8' }" variant =" outline" class =" rounded-none" >
282235 <UPageMarquee pause-on-hover :ui =" { root: '[--duration:40s]' }" >
283- <img
284- v-for =" (logo, index) in page.customers.items"
285- :key =" index"
286- v-bind =" logo"
287- class =" h-6 shrink-0 max-w-[140px] filter invert dark:invert-0"
288- loading =" lazy"
289- >
236+ <img v-for =" (logo, index) in page.customers.items" :key =" index" v-bind =" logo" class =" h-6 shrink-0 max-w-[140px] filter invert dark:invert-0" loading =" lazy" >
290237 </UPageMarquee >
291238 </UPageCTA >
292239 <UPageSection v-bind =" page.faq" :ui =" { container: 'relative' }" >
293240 <div aria-hidden =" true" class =" hidden lg:block absolute z-[-1] border-x border-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
294- <UPageAccordion
295- multiple
296- :items =" (page.faq.items as any[])"
297- class =" max-w-4xl mx-auto"
298- >
241+ <UPageAccordion multiple :items =" (page.faq.items as any[])" class =" max-w-4xl mx-auto" >
299242 <template #body =" { item , index } " >
300243 <MDC :value =" item.content" unwrap =" p" :cache-key =" `figma-faq-${index}-content`" />
301244 </template >
0 commit comments