94 lines
3.7 KiB
Vue
94 lines
3.7 KiB
Vue
<script setup lang="ts">
|
|
import { AnimatePresence, motion } from "motion-v";
|
|
import VuesaxIcon from "~/components/ui/VuesaxIcon.vue";
|
|
import DynamicIcon from "~/components/ui/DynamicIcon.vue";
|
|
import Reveal from "~/components/ui/Reveal.vue";
|
|
import SectionHead from "~/components/ui/SectionHead.vue";
|
|
import { serviceSchema } from "#shared/seo/schema";
|
|
|
|
const services = computed(() => useSiteStore().sections.services);
|
|
const seo = useSeoContext();
|
|
|
|
useJsonLd(
|
|
services.value.items.map((s) => serviceSchema(seo, { name: s.title, description: s.desc })),
|
|
"services",
|
|
);
|
|
|
|
const open = ref(0);
|
|
</script>
|
|
|
|
<template>
|
|
<section id="services" aria-labelledby="services-title" class="border-t border-line py-24 md:py-32">
|
|
<div class="mx-auto max-w-[1480px] px-5 md:px-8">
|
|
<SectionHead heading-id="services-title" v-bind="services.head" />
|
|
|
|
<div class="mt-16">
|
|
<Reveal v-for="(s, i) in services.items" :key="s.title" :delay="i * 0.05">
|
|
<div :class="`border-t border-line ${i === services.items.length - 1 ? 'border-b' : ''}`">
|
|
<button
|
|
class="group flex w-full items-center gap-5 py-7 text-start md:gap-10 md:py-9"
|
|
@click="open = open === i ? -1 : i"
|
|
>
|
|
<span
|
|
:class="`font-mono text-sm transition-colors duration-300 md:text-base ${
|
|
open === i ? 'text-accent' : 'text-muted'
|
|
}`"
|
|
>
|
|
{{ faNum(String(i + 1).padStart(2, "0")) }}
|
|
</span>
|
|
<span
|
|
:class="`flex-1 text-2xl font-black transition-colors duration-300 md:text-4xl ${
|
|
open === i ? 'text-accent' : 'text-ink group-hover:text-accent'
|
|
}`"
|
|
>
|
|
{{ s.title }}
|
|
</span>
|
|
<span
|
|
:class="`hidden size-12 shrink-0 items-center justify-center rounded-full border transition-all duration-500 md:flex ${
|
|
open === i
|
|
? 'rotate-45 border-accent bg-accent text-bg'
|
|
: 'border-line text-muted group-hover:border-accent/50 group-hover:text-accent'
|
|
}`"
|
|
>
|
|
<VuesaxIcon name="add" class="size-5" />
|
|
</span>
|
|
</button>
|
|
|
|
<AnimatePresence :initial="false">
|
|
<motion.div
|
|
v-if="open === i"
|
|
:initial="{ height: 0, opacity: 0 }"
|
|
:animate="{ height: 'auto', opacity: 1 }"
|
|
:exit="{ height: 0, opacity: 0 }"
|
|
:transition="{ duration: 0.55, ease: EASE }"
|
|
class="overflow-hidden"
|
|
>
|
|
<div class="grid gap-8 pb-10 md:grid-cols-2 md:ps-16">
|
|
<div class="flex items-start gap-4">
|
|
<span
|
|
class="flex size-12 shrink-0 items-center justify-center rounded-xl bg-accent/10 text-accent"
|
|
>
|
|
<DynamicIcon :name="s.icon" class="size-6" />
|
|
</span>
|
|
<p class="text-sm leading-8 text-muted">{{ s.desc }}</p>
|
|
</div>
|
|
<ul class="space-y-3.5">
|
|
<li
|
|
v-for="it in s.items"
|
|
:key="it"
|
|
class="flex items-center gap-3 text-sm text-ink/80"
|
|
>
|
|
<span class="size-1.5 shrink-0 rounded-full bg-accent" />
|
|
{{ it }}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
</div>
|
|
</Reveal>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|