Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 63 additions & 120 deletions docs/app/pages/figma.vue
Original file line number Diff line number Diff line change
@@ -1,67 +1,86 @@
<script setup lang="ts">
import { animate } from 'motion-v'
import { joinURL } from 'ufo'

const { data: page } = await useAsyncData('figma', () => queryCollection('figma').first())
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}

const { url } = useSiteConfig()

useSeoMeta({
title: page.value.title,
description: page.value.description,
ogTitle: page.value.title,
ogDescription: page.value.description
})

defineOgImageComponent('Docs', {
headline: 'Community'
ogDescription: page.value.description,
ogImage: joinURL(url, '/figma/og-image.png')
})

const video = ref<HTMLVideoElement | null>(null)
const played = ref(false)

const { width: windowWidth } = useWindowSize()
const isMobile = computed(() => windowWidth.value < 768)

onMounted(async () => {
// Animate cursors
await new Promise(resolve => setTimeout(resolve, 1000))
const figmaWordPosition = document.querySelector('#figma')?.getBoundingClientRect()
const nuxtWordPosition = document.querySelector('#nuxt')?.getBoundingClientRect()
const initialScrollX = window.scrollX
const initialScrollY = window.scrollY

if (figmaWordPosition && nuxtWordPosition) {
const cursor1Sequence = async () => {
await animate('#cursor1', { left: Math.round(Math.random() * window.outerWidth), top: Math.round(Math.random() * window.outerHeight) }, { duration: 0.1 }).finished
await animate('#cursor1', { opacity: 1 }, { duration: 0.3 }).finished
await animate('#cursor1', {
left: Math.round(figmaWordPosition.left + initialScrollX + figmaWordPosition.width / 2),
top: Math.round(figmaWordPosition.top + initialScrollY - figmaWordPosition.height / 4)
}, { duration: 1.5, delay: 0.2, ease: 'easeInOut' }).finished
await animate('#cursor1', { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }).finished
await animate('#cursor1', { scale: 1 }, { duration: 0.1, ease: 'easeOut' }).finished
await animate('#figma', { color: 'var(--ui-info)' }, { duration: 0.3, ease: 'easeOut' }).finished
await animate('#cursor1', {
left: Math.round(figmaWordPosition.left + initialScrollX + figmaWordPosition.width),
top: Math.round(figmaWordPosition.top + initialScrollY)
}, { duration: 0.6, ease: 'easeInOut' }).finished
}
const createCursorSequence = async (
cursorId: string,
targetWord: DOMRect,
targetColor: string,
wordId: string,
delay: number = 0
) => {
const maxWidth = isMobile.value ? windowWidth.value * 0.8 : window.outerWidth
const maxHeight = isMobile.value ? window.innerHeight * 0.6 : window.outerHeight

await animate(cursorId, {
left: Math.round(Math.random() * maxWidth),
top: Math.round(Math.random() * maxHeight)
}, { duration: 0.1, delay }).finished

await animate(cursorId, { opacity: 1 }, { duration: 0.3 }).finished

const cursor2Sequence = async () => {
await animate('#cursor2', { left: Math.round(Math.random() * window.outerWidth), top: Math.round(Math.random() * window.outerHeight) }, { duration: 0.1, delay: 0.6 }).finished
await animate('#cursor2', { opacity: 1 }, { duration: 0.3 }).finished
await animate('#cursor2', {
left: Math.round(nuxtWordPosition.left + initialScrollX + nuxtWordPosition.width / 2),
top: Math.round(nuxtWordPosition.top + initialScrollY - nuxtWordPosition.height / 4)
}, { duration: 1.5, delay: 0.2, ease: 'easeInOut' }).finished
await animate('#cursor2', { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }).finished
await animate('#cursor2', { scale: 1 }, { duration: 0.1, ease: 'easeOut' }).finished
await animate('#nuxt', { color: 'var(--ui-success)' }, { duration: 0.3, ease: 'easeOut' }).finished
await animate('#cursor2', {
left: Math.round(nuxtWordPosition.left + initialScrollX + nuxtWordPosition.width),
top: Math.round(nuxtWordPosition.top + initialScrollY)
}, { duration: 0.6, ease: 'easeInOut' }).finished
const clickPositionX = isMobile.value
? Math.round(targetWord.left + initialScrollX + targetWord.width * 0.5)
: Math.round(targetWord.left + initialScrollX + targetWord.width / 2)
const clickPositionY = isMobile.value
? Math.round(targetWord.top + initialScrollY - targetWord.height / 0.7)
: Math.round(targetWord.top + initialScrollY - targetWord.height / 1.2)

await animate(cursorId, {
left: clickPositionX,
top: clickPositionY
}, { duration: 1, delay: 0.2, ease: 'easeInOut' }).finished

await animate(cursorId, { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }).finished
await animate(cursorId, { scale: 1 }, { duration: 0.1, ease: 'easeOut' }).finished
await animate(wordId, { color: targetColor }, { duration: 0.3, ease: 'easeOut' }).finished

const finalPositionX = isMobile.value
? Math.round(targetWord.left + initialScrollX + targetWord.width * 1)
: Math.round(targetWord.left + initialScrollX + targetWord.width)
const finalPositionY = isMobile.value
? Math.round(targetWord.top + initialScrollY + targetWord.height * -1.2)
: Math.round(targetWord.top + initialScrollY - targetWord.height / 2)

await animate(cursorId, {
left: finalPositionX,
top: finalPositionY
}, { duration: 0.5, ease: 'easeInOut' }).finished
}

await Promise.all([cursor1Sequence(), cursor2Sequence()])
await Promise.all([
createCursorSequence('#cursor1', figmaWordPosition, 'var(--ui-info)', '#figma'),
createCursorSequence('#cursor2', nuxtWordPosition, 'var(--ui-success)', '#nuxt', 0.5)
])
}
})
</script>
Expand Down Expand Up @@ -148,7 +167,8 @@ onMounted(async () => {
:ui="{
container: 'lg:grid-cols-0 !gap-0 px-4 sm:px-6 lg:px-8',
wrapper: 'grid grid-cols-1 lg:grid-cols-2',
description: 'lg:mt-0' }"
description: 'mt-2'
}"
orientation="horizontal"
class="rounded-none bg-gradient-to-b from-elevated/50 to-default"
>
Expand All @@ -174,20 +194,10 @@ onMounted(async () => {
</UTabs>
</UPageSection>
<UPageSection v-bind="page.section2" orientation="horizontal" :ui="{ container: 'py-16 sm:py-16 lg:py-16' }">
<NuxtImg
v-if="page.section2.image"
v-bind="page.section2.image"
class="w-full h-auto rounded-lg"
loading="lazy"
/>
<NuxtImg v-if="page.section2.image" v-bind="page.section2.image" class="w-full h-auto rounded-lg" loading="lazy" />
</UPageSection>
<UPageSection v-bind="page.section3" orientation="horizontal" :ui="{ container: 'py-16 sm:pt-16 lg:pt-16' }">
<NuxtImg
v-if="page.section3.image"
v-bind="page.section3.image"
class="w-full h-auto rounded-lg"
loading="lazy"
/>
<NuxtImg v-if="page.section3.image" v-bind="page.section3.image" class="w-full h-auto rounded-lg" loading="lazy" />
</UPageSection>
<USeparator />
<UPageSection
Expand All @@ -207,12 +217,7 @@ onMounted(async () => {
<div aria-hidden="true" class="absolute z-[-1] border-x border-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
<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">
<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">
<NuxtImg
v-if="step.image"
v-bind="step.image"
class="rounded-sm"
loading="lazy"
/>
<NuxtImg v-if="step.image" v-bind="step.image" class="rounded-sm" loading="lazy" />
<div>
<h2 class="font-semibold inline-flex items-center gap-x-1">
<UBadge :label="index + 1" size="sm" color="neutral" variant="subtle" class="rounded-full tabular-nums" /> {{ step.title }}
Expand All @@ -225,77 +230,15 @@ onMounted(async () => {
</ul>
</UPageSection>
<UPageSection v-bind="page.features2" :ui="{ container: 'py-16 sm:py-16 lg:py-16', features: 'mt-0' }" class="border-y border-default" />
<UPageSection
v-if="page.pricing"
:title="page.pricing.title"
:description="page.pricing.description"
orientation="vertical"
:ui="{
title: 'sm:text-left',
description: 'sm:text-left',
links: 'sm:justify-start',
container: 'relative !pb-0',
wrapper: 'sm:pl-8'
}"
>
<div aria-hidden="true" class="absolute z-[-1] border-x border-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
<UPricingPlans compact class="-space-x-px">
<UPricingPlan
v-for="(plan, index) in page.pricing.plans"
:key="index"
:title="plan.title"
:description="plan.description"
:price="plan.price"
:discount="plan.discount"
:billing-period="plan.billing_period"
:billing-cycle="plan.billing_cycle"
:highlight="plan.highlight"
:features="plan.features"
:button="plan.button"
:terms="plan.terms"
class="rounded-none"
:class="plan.class"
>
<template #features>
<li v-for="(feature, i) in plan.features" :key="i" class="flex items-center gap-2 min-w-0">
<UIcon name="i-lucide-circle-check" class="size-5 shrink-0 text-primary" />
<MDC :value="feature" unwrap="p" tag="span" class="text-sm truncate text-accented" :cache-key="`figma-pricing-plan-${index}-feature-${i}`" />
</li>
</template>
<template #button>
<div class="flex flex-col w-full items-center gap-2">
<UButton v-bind="plan.button" block size="lg" />
<UButton
v-if="plan.extraButton"
v-bind="plan.extraButton"
block
size="lg"
variant="outline"
color="neutral"
/>
</div>
</template>
</UPricingPlan>
</UPricingPlans>
</UPageSection>

<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">
<UPageMarquee pause-on-hover :ui="{ root: '[--duration:40s]' }">
<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"
>
<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">
</UPageMarquee>
</UPageCTA>
<UPageSection v-bind="page.faq" :ui="{ container: 'relative' }">
<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" />
<UPageAccordion
multiple
:items="(page.faq.items as any[])"
class="max-w-4xl mx-auto"
>
<UPageAccordion multiple :items="(page.faq.items as any[])" class="max-w-4xl mx-auto">
<template #body="{ item, index }">
<MDC :value="item.content" unwrap="p" :cache-key="`figma-faq-${index}-content`" />
</template>
Expand Down
18 changes: 0 additions & 18 deletions docs/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,24 +145,6 @@ export const collections = {
image: Image
}))
}),
pricing: z.object({
title: z.string(),
description: z.string(),
plans: z.array(z.object({
title: z.string(),
description: z.string(),
price: z.string(),
discount: z.string().optional(),
billing_period: z.string().optional(),
billing_cycle: z.string().optional(),
highlight: z.boolean().optional(),
class: z.string().optional(),
features: z.array(z.string()),
terms: z.string().optional(),
button: Button.optional(),
extraButton: Button.optional()
}))
}),
customers: z.object({
title: z.string(),
items: z.array(z.object({
Expand Down
Loading