feat: redesign entire portfolio with bold modern dark theme
Complete visual overhaul of all pages and components with generous spacing, bold typography, hover effects, gradient accents, and section differentiation. Hero features animated terminal mockup and gradient text. Cards use hover transforms with brand-colored shadows. CTAs use gradient backgrounds. All i18n keys, data structures, SEO meta, and composable logic preserved.
This commit is contained in:
+67
-42
@@ -2,6 +2,7 @@
|
||||
import { techStack } from '~/data/techstack'
|
||||
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
useSeoMeta({
|
||||
title: () => t('seo.about.title'),
|
||||
@@ -64,15 +65,16 @@ const approachCards = computed(() => [
|
||||
<template>
|
||||
<div>
|
||||
<!-- Hero Section -->
|
||||
<section class="py-20 px-4 text-center">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-4xl sm:text-5xl font-bold mb-6">
|
||||
<section class="pt-16 pb-20 px-4 bg-gray-50 dark:bg-gray-900/30">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">About</span>
|
||||
<h1 class="text-4xl sm:text-5xl font-bold mt-2 mb-6 text-gray-900 dark:text-white">
|
||||
{{ t('about.title') }}
|
||||
</h1>
|
||||
<p class="text-xl text-muted mb-8">
|
||||
<p class="text-xl text-gray-500 dark:text-gray-400 mb-10 max-w-2xl mx-auto">
|
||||
{{ t('about.subtitle') }}
|
||||
</p>
|
||||
<div class="space-y-4 text-lg text-muted max-w-3xl mx-auto">
|
||||
<div class="space-y-4 text-lg text-gray-600 dark:text-gray-400 max-w-3xl mx-auto leading-relaxed">
|
||||
<p>{{ t('about.intro.content') }}</p>
|
||||
<p>{{ t('about.experience.content') }}</p>
|
||||
</div>
|
||||
@@ -80,23 +82,30 @@ const approachCards = computed(() => [
|
||||
</section>
|
||||
|
||||
<!-- Skills Section -->
|
||||
<section class="py-16 px-4">
|
||||
<section class="py-20 md:py-28 px-4">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl font-bold mb-4">{{ t('about.skills.title') }}</h2>
|
||||
<p class="text-lg text-muted max-w-2xl mx-auto">
|
||||
<div class="text-center mb-14">
|
||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Tech Stack</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('about.skills.title') }}</h2>
|
||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3 max-w-2xl mx-auto">
|
||||
{{ t('about.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Tech Categories Grid -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||
<UCard v-for="category in techCategories" :key="category.key">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<UIcon :name="category.icon" class="text-2xl text-primary" />
|
||||
<h3 class="text-xl font-bold">{{ category.title }}</h3>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<div
|
||||
v-for="category in techCategories"
|
||||
:key="category.key"
|
||||
class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center">
|
||||
<UIcon :name="category.icon" class="text-xl text-brand-600 dark:text-brand-400" />
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">{{ category.title }}</h3>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<TechBadge
|
||||
v-for="tech in techStack[category.key]"
|
||||
:key="tech.name"
|
||||
@@ -104,16 +113,18 @@ const approachCards = computed(() => [
|
||||
:show-level="true"
|
||||
/>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Operating Systems -->
|
||||
<UCard>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<UIcon name="i-lucide-monitor" class="text-2xl text-primary" />
|
||||
<h3 class="text-xl font-bold">{{ t('about.skills.systems') }}</h3>
|
||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center">
|
||||
<UIcon name="i-lucide-monitor" class="text-xl text-brand-600 dark:text-brand-400" />
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">{{ t('about.skills.systems') }}</h3>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<TechBadge
|
||||
v-for="tech in techStack.operating_systems"
|
||||
:key="tech.name"
|
||||
@@ -121,46 +132,60 @@ const approachCards = computed(() => [
|
||||
:show-level="false"
|
||||
/>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Approach Section -->
|
||||
<section class="py-16 px-4">
|
||||
<section class="py-20 md:py-28 px-4 bg-gray-50 dark:bg-gray-900/30">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl font-bold mb-4">{{ t('about.approach.title') }}</h2>
|
||||
<p class="text-lg text-muted max-w-2xl mx-auto">
|
||||
<div class="text-center mb-14">
|
||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Methodology</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('about.approach.title') }}</h2>
|
||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3 max-w-2xl mx-auto">
|
||||
{{ t('about.approach.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<UCard v-for="(card, index) in approachCards" :key="index">
|
||||
<div
|
||||
v-for="(card, index) in approachCards"
|
||||
:key="index"
|
||||
class="group rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8 transition-all duration-300 hover:border-brand-500/30 hover:shadow-lg hover:shadow-brand-500/5"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<UIcon :name="card.icon" class="text-2xl text-primary shrink-0 mt-1" />
|
||||
<div class="w-12 h-12 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0 transition-colors group-hover:bg-brand-500/20">
|
||||
<UIcon :name="card.icon" class="text-xl text-brand-600 dark:text-brand-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-bold mb-2">{{ card.title }}</h3>
|
||||
<p class="text-muted">{{ card.description }}</p>
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ card.title }}</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 leading-relaxed">{{ card.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<section class="py-16 px-4 text-center">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<h2 class="text-3xl font-bold mb-4">{{ t('about.cta.title') }}</h2>
|
||||
<p class="text-lg text-muted mb-8">{{ t('about.cta.description') }}</p>
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<UButton to="/contact" size="lg">
|
||||
{{ t('about.cta.button') }}
|
||||
</UButton>
|
||||
<UButton to="/projects" size="lg" variant="outline">
|
||||
{{ t('home.cta.viewProjects') }}
|
||||
</UButton>
|
||||
<section class="py-20 md:py-28 px-4">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="relative overflow-hidden rounded-3xl bg-gradient-to-br from-brand-600 via-brand-500 to-emerald-500 px-8 py-16 sm:px-16 sm:py-20 text-center">
|
||||
<div class="absolute top-0 left-0 w-72 h-72 bg-white/10 rounded-full -translate-x-1/2 -translate-y-1/2 blur-2xl" aria-hidden="true" />
|
||||
<div class="absolute bottom-0 right-0 w-96 h-96 bg-black/10 rounded-full translate-x-1/3 translate-y-1/3 blur-2xl" aria-hidden="true" />
|
||||
|
||||
<div class="relative z-10">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">{{ t('about.cta.title') }}</h2>
|
||||
<p class="text-lg text-white/80 mb-10 max-w-2xl mx-auto">{{ t('about.cta.description') }}</p>
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<UButton :to="localePath('/contact')" size="xl" color="white" class="font-semibold">
|
||||
{{ t('about.cta.button') }}
|
||||
</UButton>
|
||||
<UButton :to="localePath('/projects')" size="xl" variant="outline" color="white" class="font-semibold">
|
||||
{{ t('home.cta.viewProjects') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
+85
-65
@@ -18,72 +18,83 @@ useSeoMeta({
|
||||
<template>
|
||||
<div>
|
||||
<!-- Hero Section -->
|
||||
<section class="py-20 px-4 text-center">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-4xl sm:text-5xl font-bold mb-6">
|
||||
<section class="pt-16 pb-12 px-4 bg-gray-50 dark:bg-gray-900/30">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Contact</span>
|
||||
<h1 class="text-4xl sm:text-5xl font-bold mt-2 mb-6 text-gray-900 dark:text-white">
|
||||
{{ t('contact.title') }}
|
||||
</h1>
|
||||
<p class="text-xl text-muted mb-8">
|
||||
<p class="text-xl text-gray-500 dark:text-gray-400 mb-10 max-w-2xl mx-auto">
|
||||
{{ t('contact.subtitle') }}
|
||||
</p>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="flex flex-wrap justify-center gap-8">
|
||||
<div class="flex flex-wrap justify-center gap-10">
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-primary">24-48h</div>
|
||||
<div class="text-sm text-muted">{{ t('contact.stats.responseTime') }}</div>
|
||||
<div class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">24-48h</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('contact.stats.responseTime') }}</div>
|
||||
</div>
|
||||
<div class="w-px bg-gray-200 dark:bg-gray-800 hidden sm:block" />
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-primary">100%</div>
|
||||
<div class="text-sm text-muted">{{ t('contact.stats.satisfaction') }}</div>
|
||||
<div class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">100%</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('contact.stats.satisfaction') }}</div>
|
||||
</div>
|
||||
<div class="w-px bg-gray-200 dark:bg-gray-800 hidden sm:block" />
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-primary">Remote</div>
|
||||
<div class="text-sm text-muted">{{ t('contact.stats.collaboration') }}</div>
|
||||
<div class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">Remote</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('contact.stats.collaboration') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Two Column Layout -->
|
||||
<section class="py-16 px-4">
|
||||
<div class="max-w-5xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<!-- Left: Contact Form -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-2xl font-bold">{{ t('contact.form.title') }}</h2>
|
||||
</template>
|
||||
<ContactForm />
|
||||
</UCard>
|
||||
<section class="py-16 md:py-20 px-4">
|
||||
<div class="max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-5 gap-10 lg:gap-16">
|
||||
<!-- Left: Contact Form (wider) -->
|
||||
<div class="lg:col-span-3">
|
||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">{{ t('contact.form.title') }}</h2>
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Contact Info + Social -->
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="lg:col-span-2 flex flex-col gap-6">
|
||||
<!-- Contact Info -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-xl font-bold">{{ t('contact.quickContact') }}</h2>
|
||||
</template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<a :href="`mailto:${siteConfig.contact.email}`" class="flex items-center gap-3 hover:text-primary transition-colors">
|
||||
<UIcon name="i-lucide-mail" class="text-xl text-primary shrink-0" />
|
||||
<span>{{ siteConfig.contact.email }}</span>
|
||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-6">{{ t('contact.quickContact') }}</h2>
|
||||
<div class="flex flex-col gap-5">
|
||||
<a
|
||||
:href="`mailto:${siteConfig.contact.email}`"
|
||||
class="flex items-center gap-4 group"
|
||||
>
|
||||
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0 transition-colors group-hover:bg-brand-500/20">
|
||||
<UIcon name="i-lucide-mail" class="text-brand-600 dark:text-brand-400" />
|
||||
</div>
|
||||
<span class="text-gray-600 dark:text-gray-300 group-hover:text-brand-500 transition-colors">{{ siteConfig.contact.email }}</span>
|
||||
</a>
|
||||
<a :href="`tel:${siteConfig.contact.phone.replace(/\s/g, '')}`" class="flex items-center gap-3 hover:text-primary transition-colors">
|
||||
<UIcon name="i-lucide-phone" class="text-xl text-primary shrink-0" />
|
||||
<span>{{ siteConfig.contact.phone }}</span>
|
||||
<a
|
||||
:href="`tel:${siteConfig.contact.phone.replace(/\s/g, '')}`"
|
||||
class="flex items-center gap-4 group"
|
||||
>
|
||||
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0 transition-colors group-hover:bg-brand-500/20">
|
||||
<UIcon name="i-lucide-phone" class="text-brand-600 dark:text-brand-400" />
|
||||
</div>
|
||||
<span class="text-gray-600 dark:text-gray-300 group-hover:text-brand-500 transition-colors">{{ siteConfig.contact.phone }}</span>
|
||||
</a>
|
||||
<div class="flex items-center gap-3">
|
||||
<UIcon name="i-lucide-map-pin" class="text-xl text-primary shrink-0" />
|
||||
<span>{{ siteConfig.contact.location }}</span>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0">
|
||||
<UIcon name="i-lucide-map-pin" class="text-brand-600 dark:text-brand-400" />
|
||||
</div>
|
||||
<span class="text-gray-600 dark:text-gray-300">{{ siteConfig.contact.location }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<!-- Social Links -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-xl font-bold">{{ t('contact.findMeOn') }}</h2>
|
||||
</template>
|
||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-6">{{ t('contact.findMeOn') }}</h2>
|
||||
<div class="flex flex-col gap-3">
|
||||
<a
|
||||
v-for="social in siteConfig.social.filter(s => s.icon !== 'i-lucide-mail')"
|
||||
@@ -91,46 +102,55 @@ useSeoMeta({
|
||||
:href="social.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-3 hover:text-primary transition-colors"
|
||||
class="flex items-center gap-4 p-3 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors group"
|
||||
>
|
||||
<UIcon :name="social.icon" class="text-xl shrink-0" />
|
||||
<span>{{ social.name }}</span>
|
||||
<UIcon name="i-lucide-external-link" class="text-sm text-muted ml-auto" />
|
||||
<div class="w-10 h-10 rounded-xl bg-gray-100 dark:bg-gray-800 flex items-center justify-center shrink-0 group-hover:bg-brand-500/10 transition-colors">
|
||||
<UIcon :name="social.icon" class="text-gray-500 dark:text-gray-400 group-hover:text-brand-500 transition-colors" />
|
||||
</div>
|
||||
<span class="text-gray-700 dark:text-gray-300 font-medium group-hover:text-brand-500 transition-colors">{{ social.name }}</span>
|
||||
<UIcon name="i-lucide-external-link" class="text-xs text-gray-400 dark:text-gray-600 ml-auto" />
|
||||
</a>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQ Info Cards -->
|
||||
<section class="py-16 px-4">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl font-bold mb-4">{{ t('contact.faq.title') }}</h2>
|
||||
<p class="text-lg text-muted max-w-2xl mx-auto">
|
||||
<section class="py-16 md:py-20 px-4 bg-gray-50 dark:bg-gray-900/30">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-14">
|
||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Info</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('contact.faq.title') }}</h2>
|
||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3 max-w-2xl mx-auto">
|
||||
{{ t('contact.faq.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<UCard class="text-center">
|
||||
<UIcon name="i-lucide-clock" class="text-3xl text-primary mb-4 mx-auto" />
|
||||
<h3 class="text-lg font-bold mb-2">{{ t('contact.faq.responseTime.title') }}</h3>
|
||||
<p class="text-muted text-sm">{{ t('contact.faq.responseTime.description') }}</p>
|
||||
</UCard>
|
||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-8 text-center group hover:border-brand-500/30 transition-all duration-300">
|
||||
<div class="w-14 h-14 rounded-2xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mx-auto mb-5 group-hover:bg-brand-500/20 transition-colors">
|
||||
<UIcon name="i-lucide-clock" class="text-2xl text-brand-600 dark:text-brand-400" />
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ t('contact.faq.responseTime.title') }}</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">{{ t('contact.faq.responseTime.description') }}</p>
|
||||
</div>
|
||||
|
||||
<UCard class="text-center">
|
||||
<UIcon name="i-lucide-building" class="text-3xl text-primary mb-4 mx-auto" />
|
||||
<h3 class="text-lg font-bold mb-2">{{ t('contact.faq.projectTypes.title') }}</h3>
|
||||
<p class="text-muted text-sm">{{ t('contact.faq.projectTypes.description') }}</p>
|
||||
</UCard>
|
||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-8 text-center group hover:border-brand-500/30 transition-all duration-300">
|
||||
<div class="w-14 h-14 rounded-2xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mx-auto mb-5 group-hover:bg-brand-500/20 transition-colors">
|
||||
<UIcon name="i-lucide-building" class="text-2xl text-brand-600 dark:text-brand-400" />
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ t('contact.faq.projectTypes.title') }}</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">{{ t('contact.faq.projectTypes.description') }}</p>
|
||||
</div>
|
||||
|
||||
<UCard class="text-center">
|
||||
<UIcon name="i-lucide-users" class="text-3xl text-primary mb-4 mx-auto" />
|
||||
<h3 class="text-lg font-bold mb-2">{{ t('contact.faq.collaboration.title') }}</h3>
|
||||
<p class="text-muted text-sm">{{ t('contact.faq.collaboration.description') }}</p>
|
||||
</UCard>
|
||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-8 text-center group hover:border-brand-500/30 transition-all duration-300">
|
||||
<div class="w-14 h-14 rounded-2xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mx-auto mb-5 group-hover:bg-brand-500/20 transition-colors">
|
||||
<UIcon name="i-lucide-users" class="text-2xl text-brand-600 dark:text-brand-400" />
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ t('contact.faq.collaboration.title') }}</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">{{ t('contact.faq.collaboration.description') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
+104
-77
@@ -33,20 +33,21 @@ const heroStats = computed(() => [
|
||||
<template>
|
||||
<div>
|
||||
<!-- Hero Section -->
|
||||
<section class="py-20 px-4 text-center">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-4xl sm:text-5xl font-bold mb-6">
|
||||
<section class="pt-16 pb-16 px-4 bg-gray-50 dark:bg-gray-900/30">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Fiverr</span>
|
||||
<h1 class="text-4xl sm:text-5xl font-bold mt-2 mb-6 text-gray-900 dark:text-white">
|
||||
{{ t('fiverr.title') }}
|
||||
</h1>
|
||||
<p class="text-xl text-muted mb-8">
|
||||
<p class="text-xl text-gray-500 dark:text-gray-400 mb-10 max-w-2xl mx-auto">
|
||||
{{ t('fiverr.subtitle') }}
|
||||
</p>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="flex flex-wrap justify-center gap-8 mb-8">
|
||||
<div class="flex flex-wrap justify-center gap-10 mb-10">
|
||||
<div v-for="stat in heroStats" :key="stat.label" class="text-center">
|
||||
<div class="text-3xl font-bold text-primary">{{ stat.number }}</div>
|
||||
<div class="text-sm text-muted">{{ stat.label }}</div>
|
||||
<div class="text-4xl font-extrabold text-brand-500 dark:text-brand-400">{{ stat.number }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ stat.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,8 +55,9 @@ const heroStats = computed(() => [
|
||||
:to="siteConfig.fiverr.profileUrl"
|
||||
target="_blank"
|
||||
external
|
||||
size="lg"
|
||||
size="xl"
|
||||
trailing-icon="i-lucide-external-link"
|
||||
class="font-semibold"
|
||||
>
|
||||
{{ t('fiverr.profileCta') }}
|
||||
</UButton>
|
||||
@@ -63,89 +65,114 @@ const heroStats = computed(() => [
|
||||
</section>
|
||||
|
||||
<!-- Services Section -->
|
||||
<section class="py-16 px-4">
|
||||
<section class="py-20 md:py-28 px-4">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl font-bold mb-4">{{ t('fiverr.services.title') }}</h2>
|
||||
<p class="text-lg text-muted">{{ t('fiverr.services.subtitle') }}</p>
|
||||
<div class="text-center mb-14">
|
||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Services</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('fiverr.services.title') }}</h2>
|
||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3">{{ t('fiverr.services.subtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<UCard v-for="service in services" :key="service.id">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
|
||||
<div
|
||||
v-for="service in services"
|
||||
:key="service.id"
|
||||
class="group rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 overflow-hidden transition-all duration-300 hover:border-brand-500/30 hover:shadow-xl hover:shadow-brand-500/5 hover:-translate-y-1"
|
||||
>
|
||||
<!-- Service Image -->
|
||||
<NuxtImg
|
||||
:src="service.image"
|
||||
:alt="t(`fiverr.serviceData.${service.id}.title`)"
|
||||
class="w-full h-48 object-cover rounded-lg mb-4"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<!-- Price & Status Badges -->
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<UBadge color="primary" variant="subtle">
|
||||
{{ t('fiverr.pricing.startingAt') }} {{ service.price }}
|
||||
</UBadge>
|
||||
<UBadge
|
||||
:color="service.url !== '#' ? 'success' : 'warning'"
|
||||
variant="subtle"
|
||||
>
|
||||
{{ service.url !== '#' ? t('fiverr.services.available') : t('fiverr.services.comingSoon') }}
|
||||
</UBadge>
|
||||
<div class="relative overflow-hidden">
|
||||
<NuxtImg
|
||||
:src="service.image"
|
||||
:alt="t(`fiverr.serviceData.${service.id}.title`)"
|
||||
class="w-full h-52 object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/40 via-transparent to-transparent" />
|
||||
<!-- Price badge overlay -->
|
||||
<div class="absolute bottom-3 left-3">
|
||||
<span class="px-3 py-1.5 rounded-lg bg-brand-500 text-white text-sm font-bold shadow-lg">
|
||||
{{ t('fiverr.pricing.startingAt') }} {{ service.price }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Status badge -->
|
||||
<div class="absolute top-3 right-3">
|
||||
<span
|
||||
:class="service.url !== '#'
|
||||
? 'bg-green-500/90 text-white'
|
||||
: 'bg-yellow-500/90 text-white'"
|
||||
class="px-2.5 py-1 rounded-lg text-xs font-semibold shadow-lg"
|
||||
>
|
||||
{{ service.url !== '#' ? t('fiverr.services.available') : t('fiverr.services.comingSoon') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Title & Description -->
|
||||
<h3 class="text-xl font-bold mb-2">
|
||||
{{ t(`fiverr.serviceData.${service.id}.title`) }}
|
||||
</h3>
|
||||
<p class="text-muted mb-4">
|
||||
{{ t(`fiverr.serviceData.${service.id}.description`) }}
|
||||
</p>
|
||||
<!-- Content -->
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-2 group-hover:text-brand-500 transition-colors">
|
||||
{{ t(`fiverr.serviceData.${service.id}.title`) }}
|
||||
</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-5 leading-relaxed">
|
||||
{{ t(`fiverr.serviceData.${service.id}.description`) }}
|
||||
</p>
|
||||
|
||||
<!-- Action Button -->
|
||||
<UButton
|
||||
v-if="service.url !== '#'"
|
||||
:to="service.url"
|
||||
target="_blank"
|
||||
external
|
||||
size="sm"
|
||||
trailing-icon="i-lucide-external-link"
|
||||
>
|
||||
{{ t('fiverr.services.orderNow') }}
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled
|
||||
>
|
||||
{{ t('fiverr.services.comingSoon') }}
|
||||
</UButton>
|
||||
</UCard>
|
||||
<!-- Action Button -->
|
||||
<UButton
|
||||
v-if="service.url !== '#'"
|
||||
:to="service.url"
|
||||
target="_blank"
|
||||
external
|
||||
trailing-icon="i-lucide-external-link"
|
||||
class="font-semibold"
|
||||
>
|
||||
{{ t('fiverr.services.orderNow') }}
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else
|
||||
variant="outline"
|
||||
disabled
|
||||
class="font-semibold"
|
||||
>
|
||||
{{ t('fiverr.services.comingSoon') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQ Section -->
|
||||
<FAQSection
|
||||
:faqs="homeFAQs"
|
||||
:title="t('fiverr.faq.title')"
|
||||
:subtitle="t('fiverr.faq.subtitle')"
|
||||
/>
|
||||
<div class="bg-gray-50 dark:bg-gray-900/30">
|
||||
<FAQSection
|
||||
:faqs="homeFAQs"
|
||||
:title="t('fiverr.faq.title')"
|
||||
:subtitle="t('fiverr.faq.subtitle')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<section class="py-16 px-4 text-center">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<h2 class="text-3xl font-bold mb-4">{{ t('fiverr.cta.title') }}</h2>
|
||||
<p class="text-lg text-muted mb-8">{{ t('fiverr.cta.subtitle') }}</p>
|
||||
<UButton
|
||||
:to="siteConfig.fiverr.profileUrl"
|
||||
target="_blank"
|
||||
external
|
||||
size="lg"
|
||||
trailing-icon="i-lucide-external-link"
|
||||
>
|
||||
{{ t('fiverr.cta.button') }}
|
||||
</UButton>
|
||||
<section class="py-20 md:py-28 px-4">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="relative overflow-hidden rounded-3xl bg-gradient-to-br from-brand-600 via-brand-500 to-emerald-500 px-8 py-16 sm:px-16 sm:py-20 text-center">
|
||||
<div class="absolute top-0 left-0 w-72 h-72 bg-white/10 rounded-full -translate-x-1/2 -translate-y-1/2 blur-2xl" aria-hidden="true" />
|
||||
<div class="absolute bottom-0 right-0 w-96 h-96 bg-black/10 rounded-full translate-x-1/3 translate-y-1/3 blur-2xl" aria-hidden="true" />
|
||||
|
||||
<div class="relative z-10">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">{{ t('fiverr.cta.title') }}</h2>
|
||||
<p class="text-lg text-white/80 mb-10 max-w-2xl mx-auto">{{ t('fiverr.cta.subtitle') }}</p>
|
||||
<UButton
|
||||
:to="siteConfig.fiverr.profileUrl"
|
||||
target="_blank"
|
||||
external
|
||||
size="xl"
|
||||
color="white"
|
||||
trailing-icon="i-lucide-external-link"
|
||||
class="font-semibold"
|
||||
>
|
||||
{{ t('fiverr.cta.button') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
+17
-15
@@ -53,24 +53,26 @@ useHead({
|
||||
<!-- Hero Section -->
|
||||
<HeroSection />
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<!-- Featured Projects Section -->
|
||||
<!-- Featured Projects Section -->
|
||||
<div class="bg-gray-50 dark:bg-gray-900/30">
|
||||
<FeaturedProjectsSection />
|
||||
|
||||
<!-- Services Section -->
|
||||
<ServicesSection />
|
||||
|
||||
<!-- Testimonials Section -->
|
||||
<TestimonialsSection />
|
||||
|
||||
<!-- FAQ Section -->
|
||||
<FAQSection
|
||||
:faqs="homeFAQs"
|
||||
:title="t('faq.title')"
|
||||
:subtitle="t('faq.subtitle')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Services Section -->
|
||||
<ServicesSection />
|
||||
|
||||
<!-- Testimonials Section -->
|
||||
<div class="bg-gray-50 dark:bg-gray-900/30">
|
||||
<TestimonialsSection />
|
||||
</div>
|
||||
|
||||
<!-- FAQ Section -->
|
||||
<FAQSection
|
||||
:faqs="homeFAQs"
|
||||
:title="t('faq.title')"
|
||||
:subtitle="t('faq.subtitle')"
|
||||
/>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<CTASection />
|
||||
</div>
|
||||
|
||||
+182
-166
@@ -29,181 +29,197 @@ useSeoMeta({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="project" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<!-- Breadcrumb -->
|
||||
<nav class="mb-8">
|
||||
<UButton
|
||||
variant="link"
|
||||
icon="i-lucide-arrow-left"
|
||||
to="/projects"
|
||||
>
|
||||
{{ t('projects.projectDetail.backToProjects') }}
|
||||
</UButton>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Grid -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12">
|
||||
<!-- Project Image -->
|
||||
<div class="rounded-lg overflow-hidden">
|
||||
<NuxtImg
|
||||
v-if="project.image"
|
||||
:src="project.image"
|
||||
:alt="project.title"
|
||||
class="w-full h-auto object-cover"
|
||||
format="webp"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Project Info -->
|
||||
<div class="flex flex-col justify-center">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<UBadge v-if="project.category" variant="subtle">{{ project.category }}</UBadge>
|
||||
<span v-if="project.date" class="text-sm text-muted">{{ project.date }}</span>
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl font-bold mb-4">{{ project.title }}</h1>
|
||||
<p class="text-lg text-muted mb-6">{{ project.description }}</p>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<UButton
|
||||
v-if="project.demoUrl"
|
||||
:to="project.demoUrl"
|
||||
target="_blank"
|
||||
icon="i-lucide-external-link"
|
||||
>
|
||||
{{ t('projects.projectDetail.viewDemo') }}
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="project.githubUrl"
|
||||
:to="project.githubUrl"
|
||||
target="_blank"
|
||||
variant="soft"
|
||||
icon="i-lucide-github"
|
||||
>
|
||||
{{ t('projects.projectDetail.sourceCode') }}
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-for="button in project.buttons"
|
||||
:key="button.title"
|
||||
:to="button.link"
|
||||
target="_blank"
|
||||
variant="outline"
|
||||
icon="i-lucide-external-link"
|
||||
>
|
||||
{{ button.title }}
|
||||
</UButton>
|
||||
</div>
|
||||
<div v-if="project">
|
||||
<!-- Back navigation -->
|
||||
<div class="bg-gray-50 dark:bg-gray-900/30 border-b border-gray-200 dark:border-gray-800">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<UButton
|
||||
variant="ghost"
|
||||
icon="i-lucide-arrow-left"
|
||||
to="/projects"
|
||||
size="sm"
|
||||
class="text-gray-500 hover:text-gray-900 dark:hover:text-white"
|
||||
>
|
||||
{{ t('projects.projectDetail.backToProjects') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Grid: Main + Sidebar -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<!-- Main Content -->
|
||||
<div class="lg:col-span-2 space-y-12">
|
||||
<!-- About -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold mb-4">{{ t('projects.projectDetail.aboutProject') }}</h2>
|
||||
<p class="text-muted leading-relaxed">
|
||||
{{ project.longDescription || project.description }}
|
||||
</p>
|
||||
|
||||
<!-- Features -->
|
||||
<div v-if="project.features" class="mt-6">
|
||||
<h3 class="text-lg font-semibold mb-3">{{ t('projects.projectDetail.keyFeatures') }}</h3>
|
||||
<ul class="space-y-2">
|
||||
<li v-for="feature in project.features" :key="feature" class="flex items-start gap-2">
|
||||
<UIcon name="i-lucide-check" class="text-primary mt-1 shrink-0" />
|
||||
<span>{{ feature }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Hero section -->
|
||||
<section class="bg-gray-50 dark:bg-gray-900/30 pb-16 pt-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-10 lg:gap-16 items-center">
|
||||
<!-- Project Image -->
|
||||
<div class="rounded-2xl overflow-hidden border border-gray-200 dark:border-gray-800 shadow-lg">
|
||||
<NuxtImg
|
||||
v-if="project.image"
|
||||
:src="project.image"
|
||||
:alt="project.title"
|
||||
class="w-full h-auto object-cover"
|
||||
format="webp"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Technologies -->
|
||||
<section v-if="project.technologies.length">
|
||||
<h2 class="text-2xl font-bold mb-4">{{ t('projects.projectDetail.technologiesUsed') }}</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<TechBadge v-for="tech in project.technologies" :key="tech" :tech="tech" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Gallery Thumbnails -->
|
||||
<section v-if="project.gallery?.length">
|
||||
<h2 class="text-2xl font-bold mb-4">{{ t('projects.projectDetail.gallery') }}</h2>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
||||
<button
|
||||
v-for="(image, index) in project.gallery"
|
||||
:key="index"
|
||||
class="relative rounded-lg overflow-hidden group cursor-pointer"
|
||||
@click="galleryRef?.openGallery(index)"
|
||||
>
|
||||
<NuxtImg
|
||||
:src="image"
|
||||
:alt="`${project.title} - Image ${index + 1}`"
|
||||
class="w-full h-32 object-cover"
|
||||
loading="lazy"
|
||||
format="webp"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-center justify-center">
|
||||
<UIcon name="i-lucide-zoom-in" class="text-white opacity-0 group-hover:opacity-100 transition-opacity text-2xl" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="space-y-6">
|
||||
<!-- Project Info Card -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h3 class="font-semibold">{{ t('projects.projectDetail.projectInfo') }}</h3>
|
||||
</template>
|
||||
|
||||
<div class="space-y-3 text-sm">
|
||||
<div v-if="project.date" class="flex justify-between">
|
||||
<span class="text-muted">{{ t('projects.projectDetail.date') }}</span>
|
||||
<span class="font-medium">{{ project.date }}</span>
|
||||
<!-- Project Info -->
|
||||
<div class="flex flex-col justify-center space-y-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<UBadge v-if="project.category" variant="subtle" size="md">{{ project.category }}</UBadge>
|
||||
<span v-if="project.date" class="text-sm text-gray-400 dark:text-gray-500 font-medium">{{ project.date }}</span>
|
||||
</div>
|
||||
<div v-if="project.category" class="flex justify-between">
|
||||
<span class="text-muted">{{ t('projects.projectDetail.category') }}</span>
|
||||
<span class="font-medium">{{ project.category }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- Related Projects -->
|
||||
<div v-if="relatedProjects.length > 0">
|
||||
<h3 class="font-semibold mb-4">{{ t('projects.projectDetail.relatedProjects') }}</h3>
|
||||
<div class="space-y-3">
|
||||
<NuxtLink
|
||||
v-for="related in relatedProjects"
|
||||
:key="related.id"
|
||||
:to="`/project/${related.id}`"
|
||||
class="flex gap-3 p-2 rounded-lg hover:bg-elevated transition-colors"
|
||||
>
|
||||
<NuxtImg
|
||||
v-if="related.image"
|
||||
:src="related.image"
|
||||
:alt="related.title"
|
||||
width="60"
|
||||
height="45"
|
||||
class="rounded object-cover shrink-0"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<p class="font-medium text-sm truncate">{{ related.title }}</p>
|
||||
<p class="text-xs text-muted line-clamp-2">{{ related.description }}</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
<h1 class="text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white">{{ project.title }}</h1>
|
||||
<p class="text-lg text-gray-500 dark:text-gray-400 leading-relaxed">{{ project.description }}</p>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="flex flex-wrap gap-3 pt-2">
|
||||
<UButton
|
||||
v-if="project.demoUrl"
|
||||
:to="project.demoUrl"
|
||||
target="_blank"
|
||||
icon="i-lucide-external-link"
|
||||
size="lg"
|
||||
class="font-semibold"
|
||||
>
|
||||
{{ t('projects.projectDetail.viewDemo') }}
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="project.githubUrl"
|
||||
:to="project.githubUrl"
|
||||
target="_blank"
|
||||
variant="soft"
|
||||
icon="i-lucide-github"
|
||||
size="lg"
|
||||
>
|
||||
{{ t('projects.projectDetail.sourceCode') }}
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-for="button in project.buttons"
|
||||
:key="button.title"
|
||||
:to="button.link"
|
||||
target="_blank"
|
||||
variant="outline"
|
||||
icon="i-lucide-external-link"
|
||||
size="lg"
|
||||
>
|
||||
{{ button.title }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Content -->
|
||||
<section class="py-16 px-4">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-10 lg:gap-16">
|
||||
<!-- Main Content -->
|
||||
<div class="lg:col-span-2 space-y-16">
|
||||
<!-- About -->
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.aboutProject') }}</h2>
|
||||
<p class="text-gray-500 dark:text-gray-400 leading-relaxed text-lg">
|
||||
{{ project.longDescription || project.description }}
|
||||
</p>
|
||||
|
||||
<!-- Features -->
|
||||
<div v-if="project.features" class="mt-8">
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4">{{ t('projects.projectDetail.keyFeatures') }}</h3>
|
||||
<ul class="space-y-3">
|
||||
<li v-for="feature in project.features" :key="feature" class="flex items-start gap-3">
|
||||
<div class="w-6 h-6 rounded-full bg-brand-500/10 flex items-center justify-center shrink-0 mt-0.5">
|
||||
<UIcon name="i-lucide-check" class="text-brand-500 w-3.5 h-3.5" />
|
||||
</div>
|
||||
<span class="text-gray-600 dark:text-gray-300">{{ feature }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Technologies -->
|
||||
<div v-if="project.technologies.length">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.technologiesUsed') }}</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<TechBadge v-for="tech in project.technologies" :key="tech" :tech="tech" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gallery Thumbnails -->
|
||||
<div v-if="project.gallery?.length">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.gallery') }}</h2>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
||||
<button
|
||||
v-for="(image, index) in project.gallery"
|
||||
:key="index"
|
||||
class="relative rounded-xl overflow-hidden group cursor-pointer border border-gray-200 dark:border-gray-800"
|
||||
@click="galleryRef?.openGallery(index)"
|
||||
>
|
||||
<NuxtImg
|
||||
:src="image"
|
||||
:alt="`${project.title} - Image ${index + 1}`"
|
||||
class="w-full h-32 object-cover transition-transform duration-300 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
format="webp"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors duration-300 flex items-center justify-center">
|
||||
<UIcon name="i-lucide-zoom-in" class="text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300 text-xl" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="space-y-6">
|
||||
<!-- Project Info Card -->
|
||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900 p-6 sticky top-24">
|
||||
<h3 class="font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.projectInfo') }}</h3>
|
||||
|
||||
<div class="space-y-4 text-sm">
|
||||
<div v-if="project.date" class="flex justify-between items-center py-2 border-b border-gray-200 dark:border-gray-800">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('projects.projectDetail.date') }}</span>
|
||||
<span class="font-semibold text-gray-900 dark:text-white">{{ project.date }}</span>
|
||||
</div>
|
||||
<div v-if="project.category" class="flex justify-between items-center py-2">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('projects.projectDetail.category') }}</span>
|
||||
<UBadge variant="subtle" size="xs">{{ project.category }}</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Related Projects -->
|
||||
<div v-if="relatedProjects.length > 0" class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900 p-6">
|
||||
<h3 class="font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.relatedProjects') }}</h3>
|
||||
<div class="space-y-4">
|
||||
<NuxtLink
|
||||
v-for="related in relatedProjects"
|
||||
:key="related.id"
|
||||
:to="`/project/${related.id}`"
|
||||
class="flex gap-3 p-3 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors group"
|
||||
>
|
||||
<NuxtImg
|
||||
v-if="related.image"
|
||||
:src="related.image"
|
||||
:alt="related.title"
|
||||
width="60"
|
||||
height="45"
|
||||
class="rounded-lg object-cover shrink-0"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<p class="font-semibold text-sm text-gray-900 dark:text-white truncate group-hover:text-brand-500 transition-colors">{{ related.title }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 line-clamp-2 mt-0.5">{{ related.description }}</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Gallery Modal -->
|
||||
<ProjectGallery
|
||||
|
||||
+67
-52
@@ -51,64 +51,79 @@ function resetFilters() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div>
|
||||
<!-- Hero -->
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold mb-4">{{ t('projects.title') }}</h1>
|
||||
<p class="text-lg text-muted max-w-2xl mx-auto">{{ t('projects.subtitle') }}</p>
|
||||
<section class="pt-16 pb-12 px-4 bg-gray-50 dark:bg-gray-900/30">
|
||||
<div class="max-w-7xl mx-auto text-center">
|
||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Portfolio</span>
|
||||
<h1 class="text-4xl sm:text-5xl font-bold mt-2 mb-4 text-gray-900 dark:text-white">{{ t('projects.title') }}</h1>
|
||||
<p class="text-lg text-gray-500 dark:text-gray-400 max-w-2xl mx-auto">{{ t('projects.subtitle') }}</p>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="flex justify-center gap-8 mt-8">
|
||||
<div class="text-center">
|
||||
<p class="text-3xl font-bold text-primary">{{ totalProjects }}</p>
|
||||
<p class="text-sm text-muted">{{ t('nav.projects') }}</p>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="text-3xl font-bold text-primary">{{ featuredCount }}</p>
|
||||
<p class="text-sm text-muted">{{ t('home.featuredProjects.title') }}</p>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="text-3xl font-bold text-primary">{{ categories.length - 1 }}</p>
|
||||
<p class="text-sm text-muted">{{ t('projects.categories.all') }}</p>
|
||||
<!-- Stats -->
|
||||
<div class="flex justify-center gap-10 mt-10">
|
||||
<div class="text-center">
|
||||
<p class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">{{ totalProjects }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('nav.projects') }}</p>
|
||||
</div>
|
||||
<div class="w-px bg-gray-200 dark:bg-gray-800" />
|
||||
<div class="text-center">
|
||||
<p class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">{{ featuredCount }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('home.featuredProjects.title') }}</p>
|
||||
</div>
|
||||
<div class="w-px bg-gray-200 dark:bg-gray-800" />
|
||||
<div class="text-center">
|
||||
<p class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">{{ categories.length - 1 }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('projects.categories.all') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="flex flex-col sm:flex-row gap-4 items-center mb-8">
|
||||
<UInput
|
||||
v-model="searchQuery"
|
||||
icon="i-lucide-search"
|
||||
:placeholder="t('common.search') + '...'"
|
||||
class="w-full sm:w-80"
|
||||
/>
|
||||
<!-- Filters & Grid -->
|
||||
<section class="py-12 px-4">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- Filter bar -->
|
||||
<div class="flex flex-col sm:flex-row gap-4 items-start sm:items-center mb-10 p-4 rounded-2xl bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-800">
|
||||
<UInput
|
||||
v-model="searchQuery"
|
||||
icon="i-lucide-search"
|
||||
:placeholder="t('common.search') + '...'"
|
||||
class="w-full sm:w-72"
|
||||
size="md"
|
||||
/>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<UButton
|
||||
v-for="category in categories"
|
||||
:key="category"
|
||||
:variant="selectedCategory === category ? 'solid' : 'soft'"
|
||||
size="sm"
|
||||
@click="selectedCategory = category"
|
||||
>
|
||||
{{ category === 'all' ? t('projects.categories.all') : t(`projects.categories.${category.replace(/\s+/g, '').toLowerCase()}`) || category }}
|
||||
</UButton>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<UButton
|
||||
v-for="category in categories"
|
||||
:key="category"
|
||||
:variant="selectedCategory === category ? 'solid' : 'soft'"
|
||||
:color="selectedCategory === category ? 'primary' : 'neutral'"
|
||||
size="sm"
|
||||
class="font-medium"
|
||||
@click="selectedCategory = category"
|
||||
>
|
||||
{{ category === 'all' ? t('projects.categories.all') : t(`projects.categories.${category.replace(/\s+/g, '').toLowerCase()}`) || category }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Projects Grid -->
|
||||
<div v-if="filteredProjects.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
||||
<ProjectCard v-for="project in filteredProjects" :key="project.id" :project="project" />
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else class="text-center py-24">
|
||||
<div class="w-16 h-16 mx-auto mb-6 rounded-2xl bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
|
||||
<UIcon name="i-lucide-search-x" class="text-2xl text-gray-400" />
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ t('projects.noResults.title') }}</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-8 max-w-md mx-auto">{{ t('projects.noResults.description') }}</p>
|
||||
<UButton @click="resetFilters" variant="soft" size="md">
|
||||
{{ t('common.reset') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Projects Grid -->
|
||||
<div v-if="filteredProjects.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<ProjectCard v-for="project in filteredProjects" :key="project.id" :project="project" />
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else class="text-center py-16">
|
||||
<UIcon name="i-lucide-search-x" class="text-4xl text-muted mb-4" />
|
||||
<h3 class="text-lg font-semibold mb-2">{{ t('projects.noResults.title') }}</h3>
|
||||
<p class="text-muted mb-6">{{ t('projects.noResults.description') }}</p>
|
||||
<UButton @click="resetFilters" variant="soft">
|
||||
{{ t('common.reset') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user