docs: various marketing improvements (#3400)

Co-authored-by: HugoRCD <hugo.richard@epitech.eu>
This commit is contained in:
Sébastien Chopin
2025-02-26 14:25:52 +01:00
committed by GitHub
parent d787cd1a2c
commit 2e8403c7e4
25 changed files with 384 additions and 216 deletions

View File

@@ -6,6 +6,7 @@ const props = defineProps<{
links: NavigationMenuItem[]
}>()
const route = useRoute()
const config = useRuntimeConfig().public
const { module } = useSharedData()
@@ -21,7 +22,8 @@ onMounted(() => {
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const items = computed(() => props.links.map(({ icon, ...link }) => link))
const desktopLinks = computed(() => props.links.map(({ icon, ...link }) => link))
const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOpen: link.children && route.path.startsWith(link.to as string) })))
</script>
<template>
@@ -53,7 +55,7 @@ const items = computed(() => props.links.map(({ icon, ...link }) => link))
</UDropdownMenu>
</template>
<UNavigationMenu :items="items" variant="link" />
<UNavigationMenu :items="desktopLinks" variant="link" />
<template #right>
<ThemePicker />
@@ -76,7 +78,7 @@ const items = computed(() => props.links.map(({ icon, ...link }) => link))
</template>
<template #body>
<UNavigationMenu orientation="vertical" :items="links" class="-mx-2.5" />
<UNavigationMenu orientation="vertical" :items="mobileLinks" class="-mx-2.5" default-open />
<USeparator type="dashed" class="mt-4 mb-6" />

View File

@@ -1,28 +0,0 @@
<template>
<div class="relative">
<UPageCard
variant="subtle"
class="rounded-[calc(var(--ui-radius)*6)]"
>
<video
class="rounded-[calc(var(--ui-radius)*2)]"
preload="none"
poster="https://res.cloudinary.com/nuxt/video/upload/so_3.3/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.jpg"
:controls="true"
>
<source
src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.webm"
type="video/webm"
>
<source
src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.mp4"
type="video/mp4"
>
<source
src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.ogg"
type="video/ogg"
>
</video>
</UPageCard>
</div>
</template>

View File

@@ -10,6 +10,7 @@ export function useLinks() {
label: 'Components',
icon: 'i-lucide-square-code',
to: '/components',
active: route.path === '/components',
children: [{
label: 'Layout',
to: '/components#layout',

View File

@@ -54,6 +54,22 @@ const categories = [{
title: 'Overlay',
description: 'Floating UI elements like modals, dialogs, tooltips, popovers, and other components that overlay the main content.'
}]
const { y } = useWindowScroll()
onMounted(() => {
const stickyElements = document.querySelectorAll('[data-track-sticky]') as NodeListOf<HTMLElement>
watch(y, () => {
stickyElements.forEach((el) => {
const rect = el.getBoundingClientRect()
const topComputed = Number.parseInt(window.getComputedStyle(el).top || '0', 10)
if (rect.top <= topComputed) {
el.dataset.stuck = ''
} else {
delete el.dataset.stuck
}
})
}, { immediate: true })
})
</script>
<template>
@@ -62,8 +78,9 @@ const categories = [{
description="Build your Vue or Nuxt application faster with Nuxt UI and Nuxt UI Pro components. Powered by Tailwind CSS and Reka UI, delivering responsive and customizable components."
class="relative"
orientation="vertical"
:ui="{ title: 'text-balance' }"
:ui="{ title: 'text-balance', container: 'relative' }"
>
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<template #headline>
<UButton
to="https://tailwindcss.com"
@@ -102,24 +119,25 @@ const categories = [{
</template>
</UPageHero>
<UContainer>
<div v-for="category in categories" :key="category.id">
<div class="mb-4 sm:mb-6 lg:mb-8 sticky top-(--ui-header-height) bg-(--ui-bg)/75 backdrop-blur z-[1] -mx-4 px-4 sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
<div class="relative border-b border-(--ui-border) py-4 sm:py-6 lg:py-8">
<h2 class="relative text-pretty font-bold text-(--ui-text-highlighted) text-base sm:text-xl lg:text-2xl">
<a :href="`#${category.id}`" class="group lg:ps-2 lg:-ms-2">
<span class="absolute -ms-8 top-1 opacity-0 group-hover:opacity-100 group-focus:opacity-100 p-1 bg-(--ui-bg-elevated) hover:text-(--ui-primary) rounded-[calc(var(--ui-radius)*1.5)] hidden lg:flex text-(--ui-text-muted) transition">
<div data-track-sticky class="group mb-4 sm:mb-6 lg:mb-8 sticky top-[calc(var(--ui-header-height)-1px)] bg-(--ui-bg)/75 backdrop-blur z-[1]">
<div class="relative border-y border-(--ui-border) py-4 sm:not-group-[[data-stuck]]:py-6 lg:not-group-[[data-stuck]]:py-8 transition-all duration-300">
<UContainer>
<h2 class="relative text-pretty font-bold text-(--ui-text-highlighted) text-base sm:not-group-[[data-stuck]]:text-xl lg:not-group-[[data-stuck]]:text-2xl transition-all duration-300 ">
<a :href="`#${category.id}`" class="group lg:not-group-[[data-stuck]]:ps-2 lg:not-group-[[data-stuck]]:-ms-2">
<span class="absolute -ms-8 top-1 opacity-0 group-hover:opacity-100 group-focus:opacity-100 p-1 bg-(--ui-bg-elevated) hover:text-(--ui-primary) rounded-[calc(var(--ui-radius)*1.5)] hidden lg:not-group-[[data-stuck]]:flex text-(--ui-text-muted) transition">
<UIcon name="i-lucide-hash" class="size-4 shrink-0" />
</span>
{{ category.title }}
</a>
</h2>
<p class="text-pretty text-(--ui-text-muted) text-sm sm:text-base lg:text-lg mt-1 sm:mt-2 line-clamp-1">
<p class="text-pretty text-(--ui-text-muted) text-sm sm:not-group-[[data-stuck]]:text-base lg:not-group-[[data-stuck]]:text-lg mt-1 sm:not-group-[[data-stuck]]:mt-2 line-clamp-1 transition-all duration-300">
{{ category.description }}
</p>
</UContainer>
</div>
</div>
<UContainer>
<UPageGrid :id="category.id" class="xl:grid-cols-4 gap-4 sm:gap-6 lg:gap-8 pb-24 scroll-mt-[calc(97px+var(--ui-header-height))] sm:scroll-mt-[calc(133px+var(--ui-header-height))] lg:scroll-mt-[calc(165px+var(--ui-header-height))]">
<UPageCard
v-for="(component, index) in componentsPerCategory[category.id]"
@@ -149,7 +167,7 @@ const categories = [{
</div>
</UPageCard>
</UPageGrid>
</div>
</UContainer>
</div>
</UMain>
</template>

View File

@@ -124,7 +124,9 @@ onMounted(async () => {
</UButton>
</div>
</div>
<div aria-hidden="true" class="absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<Motion as-child :initial="{ height: 0 }" :animate="{ height: 'auto' }" :transition="{ delay: 0.2, duration: 1 }">
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
</Motion>
</UPageHero>
<UPageSection v-bind="page.features1" :ui="{ container: 'py-16 sm:py-16 lg:py-16', features: 'mt-0' }" class="border-y border-(--ui-border)" />
<UPageCTA

View File

@@ -7,17 +7,15 @@ pricing:
freePlan:
title: Free in development
description: Try Nuxt UI Pro for free in development, no credit card required. Upgrade when ready to deploy.
variant: soft
orientation: horizontal
button:
label: Get started for Free
to: '/getting-started/installation/pro/nuxt'
color: 'neutral'
variant: 'outline'
variant: 'subtle'
figma:
title: Figma Kit Pro
description: Get all Nuxt UI Pro components in a Figma kit to design your next application before coding. Everything you need, from wire-framing to high-fidelity web integration.
variant: soft
orientation: horizontal
price: $149 - $349
terms: Solo & Team licenses available.

View File

@@ -2,18 +2,18 @@ title: Build faster with Nuxt UI Pro.
description: A collection of premium Vue components, composables and utils built on top of Nuxt UI, oriented on structure and layout and designed to be used as building blocks for your application.
hero:
title: Build faster with Nuxt UI [Pro]{class="text-(--ui-primary)"}.
description: A collection of premium Vue components, composables and utils built on top of Nuxt UI. :br Focused on structure and layout, these **responsive components** are designed to be the perfect **building blocks for your next idea**.
description: A collection of premium Vue components, composables and utils built on top of Nuxt UI. :br Focused on structure and layout, these [responsive components]{class="text-(--ui-text)"} are designed to be the perfect [building blocks for your next idea]{class="text-(--ui-text)"}.
links:
- label: Buy a license
size: xl
to: /pro/pricing
trailing-icon: i-lucide-arrow-right
- label: Get started
icon: i-lucide-arrow-right
trailing: true
color: neutral
variant: ghost
to: /getting-started/installation/pro/nuxt
size: xl
- label: Purchase a license
size: xl
color: neutral
variant: outline
to: /pro/pricing
features:
title: Create stunning Vue applications faster.
description: Nuxt UI Pro comes packed with powerful features to help you build modern, performant, accessible and responsive Nuxt applications at record speed. From pre-built UI sections to Figma design kits, every detail is crafted to speed up your development and deliver a polished user experience.
@@ -45,18 +45,30 @@ features:
- title: Figma Design Kits
description: Match your development workflow with Nuxt UI & UI Pro Figma UI kits, ensuring a fast transition from design to code.
icon: i-simple-icons-figma
authorQuote:
quote: Nuxt UI, born from a desire to improve Vue component development, is the go-to library for building modern web interfaces. We aim to provide you with a comprehensive set of tools to create and customize your next UI while maintaining the best developer experience.
testimonial:
quote: Nuxt UI has allowed me to develop my SaaS without any prior mockups. The design quality of their components and the intelligence of the DX meant that I was able to try many different layouts for my application until I found the perfect UX for my users. Nuxt UI is the ui-kit I would have dreamed of building myself, and Nuxt UI Pro makes things even easier when you want to go further with your SaaS. Kudos to the team.
user:
name: Benjamin Canac
description: Author of Nuxt UI
to: https://github.com/benjamincanac
name: Benjamin Code
description: YouTuber and Founder
to: https://x.com/benjamincode
avatar:
src: https://github.com/benjamincanac.png
src: https://pbs.twimg.com/profile_images/1607353032420769793/I8qQSUfQ_400x400.jpg
# authorQuote:
# quote: Nuxt UI, born from a desire to improve Vue component development, is the go-to library for building modern web interfaces. We aim to provide you with a comprehensive set of tools to create and customize your next UI while maintaining the best developer experience.
# user:
# name: Benjamin Canac
# description: Author of Nuxt UI
# to: https://github.com/benjamincanac
# avatar:
# src: https://github.com/benjamincanac.png
mainSection:
title: Meet the [Pro Components]{class="text-(--ui-primary)"}.
description: Code with 50+ components and sections of Nuxt UI Pro to build your next application by reducing the amount of code you need to write.
sections:
- title: The freedom to build anything
description: Nuxt UI Pro ships with an extensive set of advanced components that cover a wide range of use-cases. Carefully crafted to reduce boilerplate code without sacrificing flexibility.
id: features
reverse: true
features:
- name: Fully customizable
description: Like Nuxt UI, change the style of any component from your App Config or customize them specifically through the ui prop.
@@ -99,9 +111,8 @@ sections:
</UApp>
</template>
```
- title: The flexibility to control your data
- title: The flexibility to display your data
description: Although you can use any data source you want, Nuxt UI Pro is fully integrated with Nuxt Content and provides additional features when the module is detected.
reverse: true
features:
- name: 'Write Markdown with ease'
description: Nuxt UI Pro overrides Nuxt Content prose components to make them awesome but also adds new ones like Callout, CodeGroup, Field, etc.
@@ -110,7 +121,7 @@ sections:
description: 'Nuxt UI Pro ships with a ready to use command palette component. No need to setup Algolia DocSearch anymore.'
icon: i-lucide-search
links:
- label: Nuxt Content integration
- label: Nuxt Content Integration
to: /getting-started/content
icon: i-lucide-arrow-right
trailing: true
@@ -144,10 +155,10 @@ cta:
title: Start with Nuxt UI Pro today!
description: Nuxt UI Pro is free in development, but you need a license to use it in production.
links:
- label: Purchase a license
- label: Buy a license
to: '/pro/pricing'
icon: i-lucide-shopping-cart
- label: License
trailing-icon: i-lucide-arrow-right
- label: Get started
to: '/getting-started/license'
trailingIcon: i-lucide-circle-help
variant: subtle
variant: ghost
color: neutral

View File

@@ -1,21 +1,20 @@
<script setup lang="ts">
import { z } from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
import { joinURL } from 'ufo'
const title = 'Activate your Nuxt UI Pro License'
const description = 'Enable Nuxt UI Pro in your production projects by activating your license key received by email and get invited to the GitHub private repository.'
const route = useRoute()
const { url } = useSiteConfig()
useSeoMeta({
title,
description,
ogTitle: `${title} - Nuxt UI Pro`,
ogDescription: description
})
defineOgImageComponent('Docs', {
headline: 'Pro'
ogDescription: description,
ogImage: joinURL(url, '/pro/og-image.png')
})
const schema = z.object({
@@ -70,13 +69,16 @@ onMounted(() => {
</script>
<template>
<UPageHero headline="License Activation" :title="title" :description="description">
<UMain>
<UPageHero headline="License Activation" :title="title" :description="description" :ui="{ container: 'relative' }">
<template #top>
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
<StarsBg />
</template>
<div>
<UCard class="lg:w-1/2 m-auto backdrop-blur-sm">
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<div class="lg:border-y border-(--ui-border)">
<UCard class="lg:w-1/2 m-auto lg:rounded-none overflow-hidden" variant="outline" :ui="{ footer: 'bg-(--ui-bg-muted)' }">
<UForm
:schema="schema"
:validate-on="['blur']"
@@ -106,10 +108,13 @@ onMounted(() => {
</UAlert>
<UAlert v-else-if="errorMessage" color="error" variant="subtle" :title="errorMessage" />
</UForm>
</UCard>
<p class="text-sm text-center text-neutral-500 dark:text-neutral-400 my-4">
If you purchased a license with multiple seats, activate the license key for each of your team members.
<template #footer>
<p class="text-sm text-center text-neutral-500 dark:text-neutral-400">
If you purchased a license with multiple seats, activate the license key for each member of your team.
</p>
</template>
</UCard>
</div>
</UPageHero>
</UMain>
</template>

View File

@@ -1,18 +1,19 @@
<script setup lang="ts">
import { joinURL } from 'ufo'
// @ts-expect-error yaml is not typed
import page from '.content/pro.yml'
// @ts-expect-error yaml is not typed
import templatesPage from '.content/templates.yml'
const { url } = useSiteConfig()
useSeoMeta({
title: page.title,
ogTitle: page.title,
ogImage: joinURL(url, '/pro/og-image.png'),
description: page.description,
ogDescription: page.description
})
defineOgImageComponent('Docs', {
headline: 'Pro'
})
</script>
<template>
@@ -20,7 +21,9 @@ defineOgImageComponent('Docs', {
<UPageHero
:links="page.hero.links"
class="relative"
orientation="horizontal"
:ui="{
container: 'relative !pb-0'
}"
>
<template #title>
<MDC :value="page.hero.title" unwrap="p" />
@@ -29,36 +32,81 @@ defineOgImageComponent('Docs', {
<MDC :value="page.hero.description" unwrap="p" />
</template>
<template #top>
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
<StarsBg />
</template>
<PromotionalVideo />
<Motion as-child :initial="{ height: 0 }" :animate="{ height: 'auto' }" :transition="{ delay: 0.2, duration: 1 }">
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
</Motion>
<Motion as-child :initial="{ opacity: 0 }" :animate="{ opacity: 1 }" :transition="{ delay: 0.6, duration: 0.6 }">
<NuxtImg src="/pro/hero.png" width="1374" height="439" alt="Nuxt UI Pro" class="w-full border-t border-x border-(--ui-border) bg-(--ui-bg-muted)" />
</Motion>
</UPageHero>
<UPageSection
v-bind="page.features"
:ui="{ title: 'text-left', description: 'text-left' }"
/>
<UPageCTA
:description="page.authorQuote.quote"
variant="soft"
variant="outline"
class="rounded-none"
:ui="{ container: 'sm:py-12 lg:py-12 sm:gap-8', description: 'before:content-[open-quote] after:content-[close-quote]' }"
:ui="{
container: 'sm:py-12 lg:py-12 sm:gap-4',
description: 'sm:text-base'
}"
>
<template #description>
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.2 }">
<MDC :value="page.testimonial.quote" unwrap="p" class="before:content-[open-quote] after:content-[close-quote] " />
</Motion>
</template>
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.3 }">
<UUser
v-bind="page.authorQuote.user"
size="xl"
v-bind="page.testimonial.user"
class="justify-center"
/>
</Motion>
</UPageCTA>
<UPageSection
v-bind="page.features"
:ui="{
title: 'text-left',
description: 'text-left',
container: 'relative',
wrapper: 'sm:px-8'
}"
class="border-t border-(--ui-border)"
>
<Motion as-child :initial="{ height: 0 }" :in-view="{ height: 'auto' }" :transition="{ delay: 0.4, duration: 1 }">
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
</Motion>
</UPageSection>
<UPageCTA
v-if="page.mainSection"
variant="naked"
: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' }"
orientation="horizontal"
class="rounded-none border-t border-(--ui-border) bg-gradient-to-b from-(--ui-bg-muted) to-(--ui-bg)"
>
<template #title>
<MDC :value="page.mainSection.title" unwrap="p" />
</template>
<template #description>
<MDC :value="page.mainSection.description" unwrap="p" />
</template>
</UPageCTA>
<UPageSection
v-for="(section, index) in page.sections"
:key="index"
v-bind="section"
:title="section.title"
:description="section.description"
:links="section.links"
:reverse="section.reverse"
:features="section.features"
orientation="horizontal"
:class="{ 'border-b border-(--ui-border)': index === page.sections.length - 1 }"
:ui="{
container: index === 0 ? 'pb-0 sm:pb-0 lg:pb-0 py-16 sm:py-16 lg:py-16' : ''
}"
>
<MDC :value="section.code" />
</UPageSection>
@@ -66,16 +114,24 @@ defineOgImageComponent('Docs', {
<UPageSection
id="templates"
v-bind="page.templates"
class="overflow-hidden"
class="overflow-hidden border-x border-(--ui-border)"
:ui="{ container: 'relative' }"
>
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<UCarousel
v-slot="{ item }"
loop
arrows
dots
wheel-gestures
:autoplay="{ delay: 3000 }"
:items="(templatesPage.templates as any[])"
:ui="{ item: 'basis-1/2', container: 'py-2' }"
:ui="{
item: 'basis-1/2',
container: 'py-2',
viewport: 'border-x border-(--ui-border)',
arrows: 'hidden 2xl:block'
}"
>
<UPageCard
:to="item.links[0].to"
@@ -94,7 +150,8 @@ defineOgImageComponent('Docs', {
:light="item.thumbnail.light"
:dark="item.thumbnail.dark"
:alt="item.title"
class="rounded-lg w-full border border-(--ui-border)"
class="rounded-lg w-full border border-(--ui-border) aspect-video"
loading="lazy"
/>
</UPageCard>
</UCarousel>
@@ -104,12 +161,30 @@ defineOgImageComponent('Docs', {
<UPageCTA
v-bind="page.cta"
variant="naked"
variant="subtle"
class="overflow-hidden"
orientation="horizontal"
>
<div class="absolute rounded-full dark:bg-(--ui-primary) blur-[250px] size-40 sm:size-50 transform -translate-x-1/2 left-1/2 -translate-y-80" />
<StarsBg />
<video
class="rounded-[var(--ui-radius)] z-10"
preload="none"
poster="https://res.cloudinary.com/nuxt/video/upload/so_3.3/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.jpg"
:controls="true"
>
<source
src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.webm"
type="video/webm"
>
<source
src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.mp4"
type="video/mp4"
>
<source
src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.ogg"
type="video/ogg"
>
</video>
</UPageCTA>
</div>
</template>

View File

@@ -1,15 +1,16 @@
<script setup lang="ts">
import { joinURL } from 'ufo'
// @ts-expect-error yaml is not typed
import page from '.content/pricing.yml'
const { url } = useSiteConfig()
useSeoMeta({
title: page.title,
ogTitle: page.title,
description: page.description,
ogDescription: page.description
})
defineOgImageComponent('Docs', {
headline: 'Pro'
ogDescription: page.description,
ogImage: joinURL(url, '/pro/og-image.png')
})
</script>
@@ -18,23 +19,24 @@ defineOgImageComponent('Docs', {
<UPageHero
class="relative"
:description="page.pricing.description"
:ui="{
container: 'relative lg:!pb-0'
}"
>
<template #title>
<MDC :value="page.pricing.title" unwrap="p" />
</template>
<template #top>
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
<StarsBg />
</template>
<UContainer>
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<div class="flex flex-col bg-(--ui-bg) gap-8 lg:gap-0">
<UPricingPlan
v-bind="page.pricing.freePlan"
class="mb-16"
variant="naked"
class="lg:rounded-none border-x border-(--ui-border) border-t border-b lg:border-b-0"
/>
<UPricingPlans
class="mb-16"
scale
>
<UPricingPlans compact>
<UPricingPlan
v-for="(plan, index) in page.pricing.plans"
:key="index"
@@ -43,15 +45,16 @@ defineOgImageComponent('Docs', {
:price="plan.price"
:billing-period="plan.billing_period"
:billing-cycle="plan.billing_cycle"
:highlight="plan.highlight"
:scale="plan.highlight"
variant="soft"
:variant="plan.highlight ? 'soft' : 'outline'"
:class="['lg:rounded-none', { 'border-2 lg:border lg:border-x-0 border-(--ui-primary) lg:border-(--ui-border)': plan.highlight }]"
:features="plan.features"
:button="plan.button"
/>
</UPricingPlans>
<UPricingPlan
v-bind="page.pricing.figma"
variant="naked"
class="lg:rounded-none border lg:border-y-0 border-(--ui-border)"
>
<template #features>
<li v-for="(feature, index) in page.pricing.figma.features" :key="index" class="flex items-center gap-2 min-w-0">
@@ -60,12 +63,13 @@ defineOgImageComponent('Docs', {
</li>
</template>
</UPricingPlan>
</UContainer>
</div>
</UPageHero>
<UPageSection
id="testimonials"
v-bind="page.testimonials"
class="border-y border-(--ui-border)"
>
<UPageMarquee pause-on-hover :ui="{ root: '[--duration:40s]' }">
<img
@@ -99,7 +103,9 @@ defineOgImageComponent('Docs', {
id="faq"
v-bind="page.faq"
class="scroll-mt-(--ui-header-height)"
:ui="{ container: 'relative' }"
>
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<UPageAccordion
multiple
:items="(page.faq.items as any[])"

View File

@@ -1,26 +1,26 @@
<script setup lang="ts">
import { joinURL } from 'ufo'
// @ts-expect-error yaml is not typed
import page from '.content/templates.yml'
const { url } = useSiteConfig()
useSeoMeta({
title: page.title,
description: page.description,
ogTitle: page.title,
ogDescription: page.description
})
defineOgImageComponent('Docs', {
headline: 'Pro'
ogDescription: page.description,
ogImage: joinURL(url, '/pro/templates/og-image.png')
})
</script>
<!-- eslint-disable vue/no-v-html -->
<template>
<div class="relative">
<UPageHero :links="page.links">
<UPageHero :links="page.links" :ui="{ container: 'relative' }">
<template #top>
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
<StarsBg />
</template>
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<template #title>
<MDC :value="page.hero.title" unwrap="p" />
@@ -38,20 +38,26 @@ defineOgImageComponent('Docs', {
:links="template.links"
:features="template.features"
orientation="horizontal"
reverse
class="*:!pt-0"
:ui="{ title: 'lg:text-4xl' }"
class="lg:border-t border-(--ui-border)"
:ui="{
title: 'lg:text-4xl',
wrapper: 'lg:py-16 lg:border-r border-(--ui-border) order-last',
container: 'lg:py-0'
}"
>
<template #description>
<MDC :value="template.description" unwrap="p" />
</template>
<div class="lg:border-x border-(--ui-border) h-full flex items-center lg:bg-(--ui-bg-muted)/20">
<Motion as-child :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0px)' }" :in-view-options="{ once: true }" :transition="{ duration: 0.5, delay: 0.2 }">
<UColorModeImage
v-if="template.thumbnail"
v-bind="template.thumbnail"
class="w-full h-auto rounded-(--ui-radius) border border-(--ui-border)"
class="w-full h-auto border lg:border-y lg:border-x-0 border-(--ui-border) rounded-(--ui-radius) lg:rounded-none"
width="656"
height="369"
loading="lazy"
/>
<UCarousel
v-else-if="template.images"
@@ -62,6 +68,7 @@ defineOgImageComponent('Docs', {
<NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" />
</UCarousel>
<Placeholder v-else class="w-full h-full aspect-video" />
</UPageSection>
</Motion>
</div>
</UPageSection>
</template>

View File

@@ -1,20 +1,21 @@
<script setup lang="ts">
import { joinURL } from 'ufo'
const { data: page } = await useAsyncData('terms', () => queryCollection('content').path('/pro/terms').first())
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
const { url } = useSiteConfig()
const title = page.value.title
const description = page.value.description
useSeoMeta({
title,
description,
ogTitle: `${title} - Nuxt UI Pro`,
ogDescription: description
})
defineOgImageComponent('Docs', {
headline: 'Pro'
ogDescription: description,
ogImage: joinURL(url, '/pro/og-image.png')
})
</script>

View File

@@ -22,7 +22,9 @@ Le paiement pour l'acquisition d'une licence Nuxt UI Pro est unique et doit êtr
## 5. Droit de rétractation et remboursement
Le Client dispose d'un droit de rétractation de 14 jours à compter de la date d'achat. Passé ce délai, aucun remboursement ne sera effectué.
Le Client dispose d'un droit de rétractation de 14 jours concernant la licence Nuxt UI Pro à compter de la date d'achat. Passé ce délai, aucun remboursement ne sera effectué.
Le Figma Pro Kit étant un produit numérique fourni sous forme de fichier ZIP, aucun remboursement ne pourra être effectué après l'achat.
## 6. Propriété intellectuelle

View File

@@ -19,6 +19,7 @@ export default defineNuxtConfig({
'@vueuse/nuxt',
'nuxt-component-meta',
'nuxt-og-image',
'motion-v/nuxt',
(_, nuxt) => {
nuxt.hook('components:dirs', (dirs) => {
dirs.unshift({ path: resolve('./app/components/content/examples'), pathPrefix: false, prefix: '', global: true })
@@ -26,6 +27,18 @@ export default defineNuxtConfig({
},
'nuxt-llms'
],
$development: {
site: {
url: 'http://localhost:3000'
}
},
$production: {
site: {
url: 'https://ui3.nuxt.dev'
}
},
devtools: { enabled: true },
app: {
head: {
@@ -47,10 +60,6 @@ export default defineNuxtConfig({
css: ['~/assets/css/main.css'],
site: {
url: 'https://ui3.nuxt.dev'
},
content: {
build: {
markdown: {

View File

@@ -18,6 +18,7 @@
"@vueuse/nuxt": "^12.7.0",
"joi": "^17.13.3",
"motion": "^12.4.7",
"motion-v": "0.11.0-beta.4",
"nuxt": "^3.15.4",
"nuxt-component-meta": "^0.10.0",
"nuxt-llms": "^0.1.0",

BIN
docs/public/og-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
docs/public/pro/hero.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 599 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

58
pnpm-lock.yaml generated
View File

@@ -291,6 +291,9 @@ importers:
motion:
specifier: ^12.4.7
version: 12.4.7
motion-v:
specifier: 0.11.0-beta.4
version: 0.11.0-beta.4(vue@3.5.13(typescript@5.6.3))
nuxt:
specifier: ^3.15.4
version: 3.15.4(@parcel/watcher@2.5.1)(@types/node@22.13.5)(better-sqlite3@11.8.1)(db0@0.2.4(better-sqlite3@11.8.1))(eslint@9.21.0(jiti@2.4.2))(ioredis@5.5.0)(lightningcss@1.29.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.32.1)(terser@5.39.0)(typescript@5.6.3)(vite@6.2.0(@types/node@22.13.5)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(yaml@2.7.0))(vue-tsc@2.2.0(typescript@5.6.3))(yaml@2.7.0)
@@ -4014,6 +4017,20 @@ packages:
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
framer-motion@11.16.6:
resolution: {integrity: sha512-iaCa44d9ngnIyj7K3PdTl6Q2lrG8l2Ioh+Em8l1335VqksB6i1VAOo+fdcF5qTcwzSoMgr15tSLgdYFbWQ31sQ==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
framer-motion@12.4.7:
resolution: {integrity: sha512-VhrcbtcAMXfxlrjeHPpWVu2+mkcoR31e02aNSR7OUS/hZAciKa8q6o3YN2mA1h+jjscRsSyKvX6E1CiY/7OLMw==}
peerDependencies:
@@ -4279,6 +4296,9 @@ packages:
resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==}
engines: {node: '>=6'}
hey-listen@1.0.8:
resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
hookable@5.5.3:
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
@@ -5106,12 +5126,23 @@ packages:
mlly@1.7.4:
resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==}
motion-dom@11.18.1:
resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==}
motion-dom@12.4.5:
resolution: {integrity: sha512-Q2xmhuyYug1CGTo0jdsL05EQ4RhIYXlggFS/yPhQQRNzbrhjKQ1tbjThx5Plv68aX31LsUQRq4uIkuDxdO5vRQ==}
motion-utils@11.18.1:
resolution: {integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==}
motion-utils@12.0.0:
resolution: {integrity: sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==}
motion-v@0.11.0-beta.4:
resolution: {integrity: sha512-FN/vh2XSzpBDpCM5oA8guX0G6gkikYbCr2gRBesMkrA4iEeHslwQiA6IFcPExFuSn1j2kgfB8vXaejZ8Xxn2NQ==}
peerDependencies:
vue: 3.5.13
motion@12.4.7:
resolution: {integrity: sha512-mhegHAbf1r80fr+ytC6OkjKvIUegRNXKLWNPrCN2+GnixlNSPwT03FtKqp9oDny1kNcLWZvwbmEr+JqVryFrcg==}
peerDependencies:
@@ -11327,6 +11358,12 @@ snapshots:
fraction.js@4.3.7: {}
framer-motion@11.16.6:
dependencies:
motion-dom: 11.18.1
motion-utils: 11.18.1
tslib: 2.8.1
framer-motion@12.4.7:
dependencies:
motion-dom: 12.4.5
@@ -11699,6 +11736,8 @@ snapshots:
hex-rgb@4.3.0: {}
hey-listen@1.0.8: {}
hookable@5.5.3: {}
hosted-git-info@7.0.2:
@@ -12674,12 +12713,31 @@ snapshots:
pkg-types: 1.3.1
ufo: 1.5.4
motion-dom@11.18.1:
dependencies:
motion-utils: 11.18.1
motion-dom@12.4.5:
dependencies:
motion-utils: 12.0.0
motion-utils@11.18.1: {}
motion-utils@12.0.0: {}
motion-v@0.11.0-beta.4(vue@3.5.13(typescript@5.6.3)):
dependencies:
'@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.6.3))
framer-motion: 11.16.6
hey-listen: 1.0.8
motion-dom: 11.18.1
vue: 3.5.13(typescript@5.6.3)
transitivePeerDependencies:
- '@emotion/is-prop-valid'
- '@vue/composition-api'
- react
- react-dom
motion@12.4.7:
dependencies:
framer-motion: 12.4.7