This commit is contained in:
2026-03-22 13:55:23 +08:00
parent c3f196ded4
commit 69099986e0
616 changed files with 38942 additions and 3 deletions

View File

@@ -0,0 +1,91 @@
<script lang="ts" setup>
import Marquee from '@/components/inspira-ui/marquee/index.vue'
import MarqueeReviewCard from '@/components/inspira-ui/marquee/review-card.vue'
const reviews = [
{
name: 'Jack',
username: '@jack',
body: 'I\'ve never seen anything like this before. It\'s amazing. I love it.',
img: 'https://avatar.vercel.sh/jack',
},
{
name: 'Jill',
username: '@jill',
body: 'I don\'t know what to say. I\'m speechless. This is amazing.',
img: 'https://avatar.vercel.sh/jill',
},
{
name: 'John',
username: '@john',
body: 'I\'m at a loss for words. This is amazing. I love it.',
img: 'https://avatar.vercel.sh/john',
},
{
name: 'Jane',
username: '@jane',
body: 'I\'m at a loss for words. This is amazing. I love it.',
img: 'https://avatar.vercel.sh/jane',
},
{
name: 'Jenny',
username: '@jenny',
body: 'I\'m at a loss for words. This is amazing. I love it.',
img: 'https://avatar.vercel.sh/jenny',
},
{
name: 'James',
username: '@james',
body: 'I\'m at a loss for words. This is amazing. I love it.',
img: 'https://avatar.vercel.sh/james',
},
]
// Split reviews into two rows
const firstRow = ref(reviews.slice(0, reviews.length / 2))
const secondRow = ref(reviews.slice(reviews.length / 2))
</script>
<template>
<h2 class="text-4xl font-black my-4 text-center">
{{ $t('marketing.evaluation.title') }}
</h2>
<h4 class="text-center mb-4">
{{ $t('marketing.evaluation.subtitle') }}
</h4>
<div
class="relative flex w-full flex-col items-center justify-center overflow-hidden"
>
<Marquee pause-on-hover class="[--duration:50s]">
<MarqueeReviewCard
v-for="review in firstRow"
:key="review.username"
:img="review.img"
:name="review.name"
:username="review.username"
:body="review.body"
/>
</Marquee>
<Marquee reverse pause-on-hover class="[--duration:50s]">
<MarqueeReviewCard
v-for="review in secondRow"
:key="review.username"
:img="review.img"
:name="review.name"
:username="review.username"
:body="review.body"
/>
</Marquee>
<!-- Left Gradient -->
<div
class="pointer-events-none absolute inset-y-0 left-0 w-1/3 bg-linear-to-r from-(--ui-bg) dark:from-(--ui-bg)"
/>
<!-- Right Gradient -->
<div
class="pointer-events-none absolute inset-y-0 right-0 w-1/3 bg-linear-to-l from-(--ui-bg) dark:from-(--ui-bg)"
/>
</div>
</template>

View File

@@ -0,0 +1,84 @@
<script lang="ts" setup>
import { Icon } from '@iconify/vue'
import { useI18n } from 'vue-i18n'
import GlowingEffect from '@/components/inspira-ui/glowing-effect.vue'
import { cn } from '@/lib/utils'
const { t } = useI18n()
const gridItems = computed(() => [
{
icon: 'lucide:box',
title: t('marketing.features.feature1.title'),
description: t('marketing.features.feature1.description'),
},
{
icon: 'lucide:settings',
title: t('marketing.features.feature2.title'),
description: t('marketing.features.feature2.description'),
},
{
icon: 'lucide:sparkles',
title: t('marketing.features.feature3.title'),
description: t('marketing.features.feature3.description'),
},
{
icon: 'lucide:search',
title: t('marketing.features.feature4.title'),
description: t('marketing.features.feature4.description'),
},
])
</script>
<template>
<div>
<h2 class="text-4xl font-bold text-center mb-8">
{{ $t('marketing.features.title') }}
</h2>
<ul
class="grid grid-cols-1 grid-rows-none gap-4 overflow-auto xl:max-h-[56rem] xl:grid-rows-2 lg:gap-4 md:grid-cols-2 md:grid-rows-3"
>
<li
v-for="item in gridItems"
:key="item.title"
:class="cn('min-h-[14rem] list-none')"
>
<div class="rounded-2.5xl relative h-full border p-2 md:rounded-3xl md:p-3">
<GlowingEffect
:spread="40"
:glow="true"
:disabled="false"
:proximity="64"
:inactive-zone="0.01"
/>
<div
class="border-0.75 relative flex h-full flex-col justify-between gap-6 overflow-hidden rounded-xl p-6 md:p-6 dark:shadow-[0px_0px_27px_0px_#2D2D2D]"
>
<div class="relative flex flex-1 flex-col justify-between gap-3">
<div class="w-fit rounded-lg border border-gray-600 p-2">
<Icon
class="size-4 text-black dark:text-neutral-500"
:icon="item.icon"
/>
</div>
<div class="space-y-3">
<h3
class="-tracking-4 text-balance pt-0.5 font-sans text-xl/[1.375rem] font-semibold text-black md:text-2xl/[1.875rem] dark:text-white"
>
{{ item.title }}
</h3>
<h2
class="font-sans text-sm/[1.125rem] text-black md:text-base/[1.375rem] dark:text-neutral-400 [&_b]:md:font-semibold [&_strong]:md:font-semibold"
>
{{ item.description }}
</h2>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</template>

View File

@@ -0,0 +1,81 @@
<script lang="ts" setup>
import Autoplay from 'embla-carousel-autoplay'
const images = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
'https://picsum.photos/640/640?random=4',
'https://picsum.photos/640/640?random=5',
'https://picsum.photos/640/640?random=6',
]
const users: { avatar: string, name: string, id: number }[] = [
{ avatar: 'https://github.com/benjamincanac.png', name: 'Benjamin Canac', id: 1 },
{ avatar: 'https://github.com/romhml.png', name: 'Benjamin Canac', id: 2 },
{ avatar: 'https://github.com/noook.png', name: 'Benjamin Canac', id: 3 },
]
</script>
<template>
<main class="flex gap-8 justify-between flex-col lg:flex-row">
<aside class="w-full lg:w-1/3">
<p class="text-4xl font-black relative">
{{ $t('marketing.hero.title') }}
</p>
<div class="font-bold mt-2 relative">
{{ $t('marketing.hero.subtitle') }}
</div>
<div class="flex gap-4 my-12 relative">
<UiButton>
{{ $t('marketing.hero.getMore') }}
</UiButton>
<img
src="@/assets/icons/arrow-dark.svg"
alt=""
class="dark:hidden block w-12 h-12 absolute top-[110%] left-8 -rotate-90"
>
<img
src="@/assets/icons/arrow-light.svg"
alt=""
class="dark:block hidden w-12 h-12 absolute top-[110%] left-8 -rotate-90"
>
</div>
<div class="flex items-center gap-2">
<div class="flex gap-2">
<UiAvatar v-for="user in users" :key="user.id">
<UiAvatarImage :src="user.avatar" />
</UiAvatar>
</div>
<span class="font-black">
{{ $t('marketing.hero.learnPeople') }}
</span>
</div>
</aside>
<aside class="w-full lg:w-2/3 lg:px-2">
<UiCarousel
:opts="{
align: 'start',
loop: true,
}"
:plugins="[Autoplay({
delay: 2000,
})]"
>
<UiCarouselContent>
<UiCarouselItem v-for="image in images" :key="image" class="basis-1/3">
<img :src="image" width="320" height="320" class="rounded-lg">
</UiCarouselItem>
</UiCarouselContent>
<UiCarouselPrevious class="hidden lg:flex" />
<UiCarouselNext class="hidden lg:flex" />
</UiCarousel>
</aside>
</main>
</template>

View File

@@ -0,0 +1,42 @@
<script lang="ts" setup>
import { Icon } from '@iconify/vue'
import Marquee from '@/components/inspira-ui/marquee/index.vue'
const types = [
{ name: 'Nuxt', icon: 'simple-icons:nuxt' },
{ name: 'Vue', icon: 'simple-icons:vitess' },
{ name: 'Vite', icon: 'simple-icons:vite' },
{ name: 'vitest', icon: 'simple-icons:vitest' },
{ name: 'vscode', icon: 'simple-icons:visualstudiocode' },
{ name: 'mysql', icon: 'simple-icons:mysql' },
{ name: 'prisma', icon: 'simple-icons:prisma' },
]
</script>
<template>
<div
class="relative flex w-full flex-col items-center justify-center overflow-hidden -rotate-3"
>
<Marquee pause-on-hover reverse class="[--duration:50s]">
<div
v-for="type in types"
:key="type.name"
class="flex items-center gap-2 mx-4"
>
<Icon :icon="type.icon" class="w-12 h-12" />
<span class="font-black text-4xl">{{ type.name }}</span>
</div>
</Marquee>
<!-- Left Gradient -->
<div
class="pointer-events-none absolute inset-y-0 left-0 w-1/3 bg-linear-to-r from-(--ui-bg) dark:from-(--ui-bg)"
/>
<!-- Right Gradient -->
<div
class="pointer-events-none absolute inset-y-0 right-0 w-1/3 bg-linear-to-l from-(--ui-bg) dark:from-(--ui-bg)"
/>
</div>
</template>

View File

@@ -0,0 +1,141 @@
<script lang="ts" setup>
import { Icon } from '@iconify/vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
interface Plan {
id: string | number
title: string
description: string
badge?: string
price: string
unit: string
discount: string
recommendation?: boolean
billing?: {
cycle: string
period: string
}
features: string[]
}
const plans = computed<Plan[]>(() => [
{
id: 1,
title: t('marketing.pricingPlans.hobby.title'),
description: t('marketing.pricingPlans.hobby.description'),
price: t('marketing.pricingPlans.hobby.price'),
discount: t('marketing.pricingPlans.hobby.discount'),
unit: t('marketing.pricingPlans.hobby.unit'),
billing: {
cycle: t('marketing.pricingPlans.hobby.billing.cycle'),
period: t('marketing.pricingPlans.hobby.billing.period'),
},
features: [
t('marketing.pricingPlans.hobby.features.feature1'),
t('marketing.pricingPlans.hobby.features.feature2'),
t('marketing.pricingPlans.hobby.features.feature3'),
t('marketing.pricingPlans.hobby.features.feature4'),
],
},
{
id: 2,
recommendation: true,
title: t('marketing.pricingPlans.starter.title'),
description: t('marketing.pricingPlans.starter.description'),
price: t('marketing.pricingPlans.starter.price'),
discount: t('marketing.pricingPlans.starter.discount'),
unit: t('marketing.pricingPlans.starter.unit'),
billing: {
cycle: t('marketing.pricingPlans.starter.billing.cycle'),
period: t('marketing.pricingPlans.starter.billing.period'),
},
features: [
t('marketing.pricingPlans.starter.features.feature1'),
t('marketing.pricingPlans.starter.features.feature2'),
t('marketing.pricingPlans.starter.features.feature3'),
t('marketing.pricingPlans.starter.features.feature4'),
t('marketing.pricingPlans.starter.features.feature5'),
],
},
{
id: 3,
title: t('marketing.pricingPlans.business.title'),
description: t('marketing.pricingPlans.business.description'),
price: t('marketing.pricingPlans.business.price'),
discount: t('marketing.pricingPlans.business.discount'),
unit: t('marketing.pricingPlans.business.unit'),
billing: {
cycle: t('marketing.pricingPlans.business.billing.cycle'),
period: t('marketing.pricingPlans.business.billing.period'),
},
features: [
t('marketing.pricingPlans.business.features.feature1'),
t('marketing.pricingPlans.business.features.feature2'),
t('marketing.pricingPlans.business.features.feature3'),
t('marketing.pricingPlans.business.features.feature4'),
t('marketing.pricingPlans.business.features.feature5'),
t('marketing.pricingPlans.business.features.feature6'),
],
},
])
</script>
<template>
<div id="pricing-plans">
<h2 class="text-center font-black my-4 text-4xl">
{{ $t('marketing.pricingPlans.title') }}
</h2>
<h4 class="text-center text-xl">
{{ $t('marketing.pricingPlans.subtitle') }}
</h4>
<div
class="flex flex-col lg:flex-row lg:items-start items-center justify-center gap-4 mt-8"
>
<UiCard
v-for="plan in plans"
:key="plan.id"
class="w-full lg:w-1/5"
:class="{
'border-2 border-primary bg-primary/10':
plan.recommendation,
}"
>
<h3 class="text-xl font-black text-center">
{{ plan.title }}
</h3>
<div class="text-sm text-center text-neutral-400">
{{ plan.description }}
</div>
<div class="flex items-top my-2 justify-center">
<div class="text-2xl font-black">
{{ plan.unit }}
<span class="text-4xl">{{ plan.price }}</span>
</div>
<div
v-if="plan.discount"
class="text-sm font-bold line-through text-neutral-400"
>
{{ plan.unit }}{{ plan.discount }}
</div>
</div>
<div class="text-sm mb-4 text-center">
<ul>
<li v-for="feature in plan.features" :key="feature" class="mb-1">
<Icon icon="carbon:checkmark" class="inline-block" />
{{ feature }}
</li>
</ul>
</div>
<div class="flex justify-center mx-8">
<UiButton block>
{{ $t('marketing.pricingPlans.buy') }}
</UiButton>
</div>
</UiCard>
</div>
</div>
</template>

View File

@@ -0,0 +1,34 @@
<script setup lang="ts">
import Ripple from '@/components/inspira-ui/ripple/index.vue'
import SignInButton from '@/components/sign-in-button.vue'
import SignUpButton from '@/components/sign-up-button.vue'
</script>
<template>
<div
class="relative flex h-[450px] w-full flex-col items-center justify-center overflow-hidden rounded-lg lg:w-full md:w-full"
>
<p class="z-10 whitespace-pre-wrap text-center text-5xl font-medium tracking-tighter text-black dark:text-white">
{{ $t('marketing.setup.title') }}
</p>
<small class="mt-2">
{{ $t('marketing.setup.subtitle') }}
</small>
<div class="flex items-center gap-3 my-2 z-100">
<SignInButton />
<SignUpButton />
</div>
<Ripple
class="bg-white/5 mask-[linear-gradient(to_bottom,white,transparent)]"
circle-class="border-[hsl(var(--primary))] bg-primary/25 blobed"
/>
</div>
</template>
<style scoped>
:deep(.blobed) {
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
}
</style>