feat(portfolio): mise à jour du site avec de nouvelles sections et améliorations SEO
- Révision des métadonnées dans index.html pour un meilleur référencement. - Ajout de nouvelles sections : FAQ, Témoignages, Services, et CTA. - Intégration de données structurées pour les FAQ et les témoignages. - Amélioration du fichier robots.txt pour un meilleur contrôle d'indexation. - Mise à jour du sitemap.xml avec de nouvelles URLs. - Ajout de nouveaux composants Vue.js pour les sections de témoignages et de services. - Amélioration des styles CSS pour une meilleure présentation des sections. - Ajout de la gestion des dates et des témoignages dans le composant testimonials.
This commit is contained in:
70
src/components/ServiceFAQ.vue
Normal file
70
src/components/ServiceFAQ.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<section class="faq-section">
|
||||
<div class="container">
|
||||
<div class="faq-header text-center mb-2xl">
|
||||
<h2 class="section-title">{{ title }}</h2>
|
||||
<p class="section-subtitle">{{ subtitle }}</p>
|
||||
</div>
|
||||
|
||||
<div class="faq-grid">
|
||||
<div v-for="(faq, index) in faqs" :key="index" class="faq-item" :class="{ 'active': activeIndex === index }">
|
||||
<button class="faq-question" @click="toggleFAQ(index)" :aria-expanded="activeIndex === index">
|
||||
<span class="question-text">{{ faq.question }}</span>
|
||||
<svg class="faq-icon" :class="{ 'rotated': activeIndex === index }" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="faq-answer" :class="{ 'open': activeIndex === index }">
|
||||
<div class="answer-content">
|
||||
<p v-html="faq.answer"></p>
|
||||
<div v-if="faq.features" class="faq-features">
|
||||
<h4>{{ t('faq.keyPoints') }}</h4>
|
||||
<ul>
|
||||
<li v-for="feature in faq.features" :key="feature">{{ feature }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface FAQ {
|
||||
question: string
|
||||
answer: string
|
||||
features?: string[]
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
subtitle: string
|
||||
faqs: FAQ[]
|
||||
ctaTitle: string
|
||||
ctaSubtitle: string
|
||||
ctaText: string
|
||||
ctaLink: string
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const activeIndex = ref<number | null>(null)
|
||||
|
||||
const toggleFAQ = (index: number) => {
|
||||
activeIndex.value = activeIndex.value === index ? null : index
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import './styles/ServiceFAQ.css';
|
||||
</style>
|
76
src/components/TestimonialsSection.vue
Normal file
76
src/components/TestimonialsSection.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<section class="testimonials-section">
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="testimonials-header">
|
||||
<h2 class="section-title">{{ title }}</h2>
|
||||
<p class="section-subtitle">{{ subtitle }}</p>
|
||||
|
||||
<!-- Stats -->
|
||||
<TestimonialsStats :stats="stats" :labels="statsLabels" />
|
||||
</div>
|
||||
|
||||
<!-- Testimonials Grid -->
|
||||
<div class="testimonials-grid">
|
||||
<TestimonialCard v-for="(testimonial, index) in testimonials" :key="index" :testimonial="testimonial"
|
||||
:class="{ 'featured': testimonial.featured }" />
|
||||
</div>
|
||||
|
||||
<!-- CTA -->
|
||||
<TestimonialsCTA :title="ctaTitle" :subtitle="ctaSubtitle" :text="ctaText" :link="ctaLink"
|
||||
:reviews-link="reviewsLink" :reviews-text="reviewsText" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TestimonialsStats from '@/components/testimonials/TestimonialsStats.vue'
|
||||
import TestimonialCard from '@/components/testimonials/TestimonialCard.vue'
|
||||
import TestimonialsCTA from '@/components/testimonials/TestimonialsCTA.vue'
|
||||
|
||||
interface Testimonial {
|
||||
name: string
|
||||
role: string
|
||||
company: string
|
||||
avatar: string
|
||||
rating: number
|
||||
content: string
|
||||
date: string
|
||||
platform: string
|
||||
featured?: boolean
|
||||
project_type: string
|
||||
results?: string[]
|
||||
}
|
||||
|
||||
interface Stats {
|
||||
totalReviews: number
|
||||
averageRating: number
|
||||
projectsCompleted: number
|
||||
}
|
||||
|
||||
interface StatsLabels {
|
||||
clients: string
|
||||
rating: string
|
||||
projects: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
subtitle: string
|
||||
testimonials: Testimonial[]
|
||||
stats: Stats
|
||||
statsLabels: StatsLabels
|
||||
ctaTitle: string
|
||||
ctaSubtitle: string
|
||||
ctaText: string
|
||||
ctaLink: string
|
||||
reviewsLink: string
|
||||
reviewsText: string
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import '@/components/styles/TestimonialsSection.css';
|
||||
</style>
|
16
src/components/sections/CTASection.vue
Normal file
16
src/components/sections/CTASection.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<SectionCTA :question="t('home.cta2.title')" :description="t('home.cta2.subtitle')"
|
||||
:primary-text="t('home.cta2.startProject')" primary-link="/contact" :secondary-text="t('home.cta2.learnMore')"
|
||||
secondary-link="/about" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import SectionCTA from '@/components/shared/SectionCTA.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
48
src/components/sections/FeaturedProjectsSection.vue
Normal file
48
src/components/sections/FeaturedProjectsSection.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="text-center mb-2xl">
|
||||
<h2 class="mb-lg">{{ t('home.featuredProjects.title') }}</h2>
|
||||
<p class="text-xl text-secondary max-w-2xl mx-auto">
|
||||
{{ t('home.featuredProjects.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="projects-grid">
|
||||
<ProjectCard v-for="project in featuredProjects" :key="project.id" :project="project"
|
||||
class="animate-fade-in-up" />
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<CTAButtons layout="stack">
|
||||
<RouterLink to="/projects" class="btn btn-secondary">
|
||||
{{ t('home.featuredProjects.viewAll') }}
|
||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
|
||||
</svg>
|
||||
</RouterLink>
|
||||
</CTAButtons>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { useProjects } from '@/composables/useProjects'
|
||||
import ProjectCard from '@/components/ProjectCard.vue'
|
||||
import CTAButtons from '@/components/shared/CTAButtons.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { projects } = useProjects()
|
||||
|
||||
// Featured projects
|
||||
const featuredProjects = computed(() => {
|
||||
return projects.value.filter(project => project.featured).slice(0, 3)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import '@/components/styles/FeaturedProjectsSection.css';
|
||||
</style>
|
46
src/components/sections/HeroSection.vue
Normal file
46
src/components/sections/HeroSection.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<div class="hero-content animate-fade-in-up">
|
||||
<h1 class="hero-title">
|
||||
{{ t('home.title') }}
|
||||
</h1>
|
||||
<p class="hero-subtitle">
|
||||
{{ t('home.subtitle') }}
|
||||
</p>
|
||||
<CTAButtons layout="columns">
|
||||
<RouterLink to="/projects" class="btn btn-primary btn-lg">
|
||||
{{ t('home.cta.viewProjects') }}
|
||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6">
|
||||
</path>
|
||||
</svg>
|
||||
</RouterLink>
|
||||
<RouterLink to="/fiverr" class="btn btn-secondary btn-lg">
|
||||
{{ t('nav.fiverr') }}
|
||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z">
|
||||
</path>
|
||||
</svg>
|
||||
</RouterLink>
|
||||
<RouterLink to="/contact" class="btn btn-secondary btn-lg">
|
||||
{{ t('home.cta.contactMe') }}
|
||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z">
|
||||
</path>
|
||||
</svg>
|
||||
</RouterLink>
|
||||
</CTAButtons>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import CTAButtons from '@/components/shared/CTAButtons.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
70
src/components/sections/ServicesSection.vue
Normal file
70
src/components/sections/ServicesSection.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<section class="services-section">
|
||||
<div class="container">
|
||||
<div class="text-center mb-2xl">
|
||||
<h2 class="mb-lg">{{ t('home.services.title') }}</h2>
|
||||
<p class="text-xl text-secondary max-w-2xl mx-auto">
|
||||
{{ t('home.services.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="services-grid">
|
||||
<div v-for="(service, index) in services" :key="index" class="card animate-fade-in-up"
|
||||
:style="{ 'animation-delay': `${index * 0.1}s` }">
|
||||
<div class="card-body">
|
||||
<div class="flex items-start">
|
||||
<div class="service-icon" :style="{ background: service.color, color: 'var(--text-inverse)' }">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="service.icon"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold mb-md">{{ service.title }}</h3>
|
||||
<p class="text-secondary">{{ service.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// Services data
|
||||
const services = computed(() => [
|
||||
{
|
||||
icon: 'M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z',
|
||||
color: 'var(--color-primary)',
|
||||
title: t('home.services.webDev.title'),
|
||||
description: t('home.services.webDev.description')
|
||||
},
|
||||
{
|
||||
icon: 'M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z',
|
||||
color: 'var(--color-secondary)',
|
||||
title: t('home.services.mobileApps.title'),
|
||||
description: t('home.services.mobileApps.description')
|
||||
},
|
||||
{
|
||||
icon: 'M13 10V3L4 14h7v7l9-11h-7z',
|
||||
color: 'var(--color-success)',
|
||||
title: t('home.services.optimization.title'),
|
||||
description: t('home.services.optimization.description')
|
||||
},
|
||||
{
|
||||
icon: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z',
|
||||
color: 'var(--color-warning)',
|
||||
title: t('home.services.maintenance.title'),
|
||||
description: t('home.services.maintenance.description')
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import '@/components/styles/ServicesSection.css';
|
||||
</style>
|
36
src/components/shared/CTAButtons.vue
Normal file
36
src/components/shared/CTAButtons.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="cta-buttons-container">
|
||||
<div class="cta-buttons" :class="layoutClass">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
layout?: 'columns' | 'row' | 'stack'
|
||||
maxButtons?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
layout: 'columns',
|
||||
maxButtons: 3
|
||||
})
|
||||
|
||||
const layoutClass = computed(() => {
|
||||
switch (props.layout) {
|
||||
case 'row':
|
||||
return 'cta-buttons--row'
|
||||
case 'stack':
|
||||
return 'cta-buttons--stack'
|
||||
default:
|
||||
return 'cta-buttons--columns'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import '@/components/styles/CTAButtons.css';
|
||||
</style>
|
58
src/components/shared/SectionCTA.vue
Normal file
58
src/components/shared/SectionCTA.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="section-cta">
|
||||
<div class="cta-content">
|
||||
<h3 class="cta-question">{{ question }}</h3>
|
||||
<p class="cta-description">{{ description }}</p>
|
||||
|
||||
<CTAButtons layout="columns">
|
||||
<RouterLink :to="primaryLink" class="btn btn-primary btn-lg">
|
||||
{{ primaryText }}
|
||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z">
|
||||
</path>
|
||||
</svg>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink v-if="secondaryText && secondaryLink" :to="secondaryLink" class="btn btn-secondary btn-lg">
|
||||
{{ secondaryText }}
|
||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6">
|
||||
</path>
|
||||
</svg>
|
||||
</RouterLink>
|
||||
|
||||
<a v-if="externalText && externalLink" :href="externalLink" class="btn btn-secondary btn-lg" target="_blank"
|
||||
rel="noopener">
|
||||
{{ externalText }}
|
||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</CTAButtons>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import CTAButtons from '@/components/shared/CTAButtons.vue'
|
||||
|
||||
interface Props {
|
||||
question: string
|
||||
description: string
|
||||
primaryText: string
|
||||
primaryLink: string
|
||||
secondaryText?: string
|
||||
secondaryLink?: string
|
||||
externalText?: string
|
||||
externalLink?: string
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import '@/components/styles/SectionCTA.css';
|
||||
</style>
|
78
src/components/styles/CTAButtons.css
Normal file
78
src/components/styles/CTAButtons.css
Normal file
@@ -0,0 +1,78 @@
|
||||
/* CTAButtons Styles */
|
||||
.cta-buttons-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cta-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Column Layout (Default) */
|
||||
.cta-buttons--columns {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.cta-buttons--columns {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.cta-buttons--columns {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* Row Layout */
|
||||
.cta-buttons--row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.cta-buttons--row {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* Stack Layout */
|
||||
.cta-buttons--stack {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Button Styling */
|
||||
.cta-buttons :deep(.btn) {
|
||||
white-space: nowrap;
|
||||
min-width: fit-content;
|
||||
flex: 0 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.cta-buttons--columns :deep(.btn) {
|
||||
flex: 1 1 auto;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.cta-buttons--row :deep(.btn) {
|
||||
flex: 0 1 auto;
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
.cta-buttons :deep(.btn) {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
21
src/components/styles/FeaturedProjectsSection.css
Normal file
21
src/components/styles/FeaturedProjectsSection.css
Normal file
@@ -0,0 +1,21 @@
|
||||
/* FeaturedProjectsSection Styles */
|
||||
.projects-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.projects-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.projects-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
65
src/components/styles/SectionCTA.css
Normal file
65
src/components/styles/SectionCTA.css
Normal file
@@ -0,0 +1,65 @@
|
||||
/* SectionCTA Styles */
|
||||
.section-cta {
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
||||
border-radius: 20px;
|
||||
padding: 48px 32px;
|
||||
text-align: center;
|
||||
margin: 64px 0;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.section-cta:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.cta-content {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.cta-question {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.cta-description {
|
||||
font-size: 1.125rem;
|
||||
color: #64748b;
|
||||
margin-bottom: 32px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.section-cta {
|
||||
padding: 32px 24px;
|
||||
margin: 48px 0;
|
||||
}
|
||||
|
||||
.cta-question {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.cta-description {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark Theme */
|
||||
.dark .section-cta {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
border-color: #475569;
|
||||
}
|
||||
|
||||
.dark .cta-question {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.dark .cta-description {
|
||||
color: #94a3b8;
|
||||
}
|
366
src/components/styles/ServiceFAQ.css
Normal file
366
src/components/styles/ServiceFAQ.css
Normal file
@@ -0,0 +1,366 @@
|
||||
.faq-section {
|
||||
padding: 4rem 0;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.faq-header {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: 1.25rem;
|
||||
color: #64748b;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.faq-grid {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.faq-item:hover {
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.faq-item.active {
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
width: 100%;
|
||||
padding: 1.5rem;
|
||||
background: none;
|
||||
border: none;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.faq-question:hover {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.faq-item.active .faq-question {
|
||||
color: #3b82f6;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
flex: 1;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.faq-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transition: transform 0.3s ease;
|
||||
color: #3b82f6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.faq-icon.rotated {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.faq-answer.open {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.answer-content {
|
||||
padding: 0 1.5rem 1.5rem;
|
||||
color: #64748b;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.answer-content p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.faq-features {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background: #f1f5f9;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
|
||||
.faq-features h4 {
|
||||
margin-bottom: 0.75rem;
|
||||
color: #3b82f6;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.faq-features ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.faq-features li {
|
||||
padding: 0.25rem 0;
|
||||
position: relative;
|
||||
padding-left: 1.5rem;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.faq-features li::before {
|
||||
content: '✅';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0.25rem;
|
||||
}
|
||||
|
||||
.faq-cta {
|
||||
background: white;
|
||||
padding: 3rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
margin-top: 3rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.cta-subtitle {
|
||||
color: #64748b;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Dark theme support using CSS classes */
|
||||
:root.dark .faq-section {
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
:root.dark .section-title {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
:root.dark .section-subtitle {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
:root.dark .faq-item {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
}
|
||||
|
||||
:root.dark .faq-item.active {
|
||||
border-color: #60a5fa;
|
||||
}
|
||||
|
||||
:root.dark .faq-question {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
:root.dark .faq-question:hover {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
:root.dark .faq-item.active .faq-question {
|
||||
color: #60a5fa;
|
||||
border-bottom-color: #334155;
|
||||
}
|
||||
|
||||
:root.dark .faq-icon {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
:root.dark .answer-content {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
:root.dark .faq-features {
|
||||
background: #0f172a;
|
||||
border-left-color: #60a5fa;
|
||||
}
|
||||
|
||||
:root.dark .faq-features h4 {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
:root.dark .faq-features li {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
:root.dark .faq-cta {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
}
|
||||
|
||||
:root.dark .cta-title {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
:root.dark .cta-subtitle {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* Light theme explicit styles (optional but good for clarity) */
|
||||
:root:not(.dark) .faq-section {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
:root:not(.dark) .section-title {
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
:root:not(.dark) .section-subtitle {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
:root:not(.dark) .faq-item {
|
||||
background: white;
|
||||
border-color: #e2e8f0;
|
||||
}
|
||||
|
||||
:root:not(.dark) .faq-item.active {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
:root:not(.dark) .faq-question {
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
:root:not(.dark) .faq-question:hover {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
:root:not(.dark) .faq-item.active .faq-question {
|
||||
color: #3b82f6;
|
||||
border-bottom-color: #e2e8f0;
|
||||
}
|
||||
|
||||
:root:not(.dark) .faq-icon {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
:root:not(.dark) .answer-content {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
:root:not(.dark) .faq-features {
|
||||
background: #f1f5f9;
|
||||
border-left-color: #3b82f6;
|
||||
}
|
||||
|
||||
:root:not(.dark) .faq-features h4 {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
:root:not(.dark) .faq-features li {
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
:root:not(.dark) .faq-cta {
|
||||
background: white;
|
||||
border-color: #e2e8f0;
|
||||
}
|
||||
|
||||
:root:not(.dark) .cta-title {
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
:root:not(.dark) .cta-subtitle {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.faq-section {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
padding: 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.answer-content {
|
||||
padding: 0 1rem 1rem;
|
||||
}
|
||||
|
||||
.faq-cta {
|
||||
padding: 2rem 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.cta-subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.answer-content {
|
||||
padding: 0 0.75rem 0.75rem;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
30
src/components/styles/ServicesSection.css
Normal file
30
src/components/styles/ServicesSection.css
Normal file
@@ -0,0 +1,30 @@
|
||||
/* ServicesSection Styles */
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.services-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.services-section {
|
||||
background: var(--bg-secondary);
|
||||
padding: 5rem 0;
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
330
src/components/styles/TestimonialCard.css
Normal file
330
src/components/styles/TestimonialCard.css
Normal file
@@ -0,0 +1,330 @@
|
||||
/* Testimonial Card - Clean Modern Design */
|
||||
.testimonial-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #e2e8f0;
|
||||
height: fit-content;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.testimonial-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.testimonial-card.featured {
|
||||
border: 2px solid #3b82f6;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.testimonial-card.featured::after {
|
||||
content: '⭐ Featured';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 0 14px 0 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.testimonial-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.client-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.client-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.client-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.client-name {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin-bottom: 4px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.client-role {
|
||||
font-size: 0.875rem;
|
||||
color: #3b82f6;
|
||||
font-weight: 500;
|
||||
margin-bottom: 2px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.client-company {
|
||||
font-size: 0.75rem;
|
||||
color: #64748b;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Rating */
|
||||
.rating {
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.stars {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.star {
|
||||
font-size: 1rem;
|
||||
color: #fbbf24;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
.star:not(.filled) {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
.rating-text {
|
||||
font-size: 0.75rem;
|
||||
color: #64748b;
|
||||
font-weight: 600;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.testimonial-content {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.testimonial-content blockquote {
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
color: #374151;
|
||||
margin-bottom: 20px;
|
||||
font-style: normal;
|
||||
position: relative;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Project Info */
|
||||
.project-info {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.project-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #eff6ff;
|
||||
color: #1e40af;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.project-type {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Results */
|
||||
.results {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.results h5 {
|
||||
font-size: 0.875rem;
|
||||
color: #059669;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.results ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.results li {
|
||||
font-size: 0.875rem;
|
||||
color: #64748b;
|
||||
padding: 2px 0;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.results li::before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #059669;
|
||||
font-weight: bold;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.testimonial-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
gap: 12px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.badges {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge.featured {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.badge.platform {
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.testimonial-date {
|
||||
font-size: 0.75rem;
|
||||
color: #94a3b8;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.testimonial-card {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.testimonial-header {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.client-info {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rating {
|
||||
align-self: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.testimonial-footer {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark Theme */
|
||||
.dark .testimonial-card {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dark .testimonial-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.dark .testimonial-card.featured {
|
||||
border-color: #60a5fa;
|
||||
}
|
||||
|
||||
.dark .testimonial-card.featured::after {
|
||||
background: #60a5fa;
|
||||
}
|
||||
|
||||
.dark .client-name {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.dark .client-role {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.dark .client-company {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.dark .rating-text {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.dark .testimonial-content blockquote {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.dark .project-tag {
|
||||
background: #1e40af;
|
||||
color: #bfdbfe;
|
||||
}
|
||||
|
||||
.dark .results h5 {
|
||||
color: #34d399;
|
||||
}
|
||||
|
||||
.dark .results li {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.dark .results li::before {
|
||||
color: #34d399;
|
||||
}
|
||||
|
||||
.dark .testimonial-footer {
|
||||
border-top-color: #334155;
|
||||
}
|
||||
|
||||
.dark .badge.featured {
|
||||
background: #78350f;
|
||||
color: #fcd34d;
|
||||
}
|
||||
|
||||
.dark .badge.platform {
|
||||
background: #334155;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.dark .testimonial-date {
|
||||
color: #64748b;
|
||||
}
|
58
src/components/styles/TestimonialsCTA.css
Normal file
58
src/components/styles/TestimonialsCTA.css
Normal file
@@ -0,0 +1,58 @@
|
||||
/* Testimonials CTA - Clean Modern Design */
|
||||
.testimonials-cta {
|
||||
text-align: center;
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 48px 32px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #e2e8f0;
|
||||
margin-top: 40px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin-bottom: 12px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.cta-subtitle {
|
||||
font-size: 1rem;
|
||||
color: #64748b;
|
||||
margin-bottom: 32px;
|
||||
line-height: 1.6;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.testimonials-cta {
|
||||
padding: 32px 24px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.cta-subtitle {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark Theme */
|
||||
.dark .testimonials-cta {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dark .cta-title {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.dark .cta-subtitle {
|
||||
color: #94a3b8;
|
||||
}
|
80
src/components/styles/TestimonialsSection.css
Normal file
80
src/components/styles/TestimonialsSection.css
Normal file
@@ -0,0 +1,80 @@
|
||||
/* Testimonials Section - Clean Modern Design */
|
||||
.testimonials-section {
|
||||
padding: 80px 0;
|
||||
background: #f8fafc;
|
||||
min-height: 100vh;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.testimonials-header {
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
color: #1e293b;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: -0.025em;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: 1.125rem;
|
||||
color: #64748b;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 40px;
|
||||
line-height: 1.7;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Dark Theme */
|
||||
.dark .testimonials-section {
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
.dark .section-title {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.dark .section-subtitle {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.testimonials-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 30px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.testimonials-section {
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.testimonials-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
72
src/components/styles/TestimonialsStats.css
Normal file
72
src/components/styles/TestimonialsStats.css
Normal file
@@ -0,0 +1,72 @@
|
||||
/* Testimonials Stats - Clean Simple Design */
|
||||
.testimonials-stats {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
margin-top: 40px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
background: white;
|
||||
padding: 30px 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
min-width: 140px;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 900;
|
||||
color: #3b82f6;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.875rem;
|
||||
color: #64748b;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Dark Theme */
|
||||
.dark .stat-item {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dark .stat-number {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.dark .stat-label {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.testimonials-stats {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
padding: 20px 16px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
98
src/components/testimonials/TestimonialCard.vue
Normal file
98
src/components/testimonials/TestimonialCard.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="testimonial-card">
|
||||
<!-- Header -->
|
||||
<div class="testimonial-header">
|
||||
<div class="client-info">
|
||||
<div class="client-avatar">
|
||||
<img :src="testimonial.avatar" :alt="testimonial.name" loading="lazy" />
|
||||
</div>
|
||||
<div class="client-details">
|
||||
<h4 class="client-name">{{ testimonial.name }}</h4>
|
||||
<p class="client-role">{{ testimonial.role }}</p>
|
||||
<p class="client-company">{{ testimonial.company }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rating -->
|
||||
<div class="rating">
|
||||
<div class="stars">
|
||||
<span v-for="star in 5" :key="star" class="star" :class="{ 'filled': star <= testimonial.rating }">
|
||||
⭐
|
||||
</span>
|
||||
</div>
|
||||
<span class="rating-text">{{ testimonial.rating }}/5</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="testimonial-content">
|
||||
<blockquote>
|
||||
{{ testimonial.content }}
|
||||
</blockquote>
|
||||
|
||||
<!-- Project Info -->
|
||||
<div v-if="testimonial.project_type" class="project-info">
|
||||
<div class="project-tag">
|
||||
<span class="project-type">{{ testimonial.project_type }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results -->
|
||||
<div v-if="testimonial.results" class="results">
|
||||
<h5>{{ t('testimonials.card.results') }}</h5>
|
||||
<ul>
|
||||
<li v-for="result in testimonial.results" :key="result">
|
||||
{{ result }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="testimonial-footer">
|
||||
<div class="badges">
|
||||
<span v-if="testimonial.featured" class="badge featured">
|
||||
🏆 {{ t('testimonials.card.featured') }}
|
||||
</span>
|
||||
<span class="badge platform">
|
||||
{{ testimonial.platform }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="testimonial-date">
|
||||
{{ formatRelativeTime(testimonial.date) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { useDateFormat } from '@/composables/useDateFormat'
|
||||
|
||||
interface Testimonial {
|
||||
name: string
|
||||
role: string
|
||||
company: string
|
||||
avatar: string
|
||||
rating: number
|
||||
content: string
|
||||
date: string
|
||||
platform: string
|
||||
featured?: boolean
|
||||
project_type: string
|
||||
results?: string[]
|
||||
}
|
||||
|
||||
interface Props {
|
||||
testimonial: Testimonial
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { formatRelativeTime } = useDateFormat()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import '@/components/styles/TestimonialCard.css';
|
||||
</style>
|
41
src/components/testimonials/TestimonialsCTA.vue
Normal file
41
src/components/testimonials/TestimonialsCTA.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="testimonials-cta">
|
||||
<h3 class="cta-title">{{ title }}</h3>
|
||||
<p class="cta-subtitle">{{ subtitle }}</p>
|
||||
<CTAButtons layout="row">
|
||||
<RouterLink :to="link" class="btn btn-primary btn-lg">
|
||||
{{ text }}
|
||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
||||
</svg>
|
||||
</RouterLink>
|
||||
<a :href="reviewsLink" class="btn btn-secondary btn-lg" target="_blank" rel="noopener">
|
||||
{{ reviewsText }}
|
||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
</svg>
|
||||
</a>
|
||||
</CTAButtons>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import CTAButtons from '@/components/shared/CTAButtons.vue'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
subtitle: string
|
||||
text: string
|
||||
link: string
|
||||
reviewsLink: string
|
||||
reviewsText: string
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import '@/components/styles/TestimonialsCTA.css';
|
||||
</style>
|
41
src/components/testimonials/TestimonialsStats.vue
Normal file
41
src/components/testimonials/TestimonialsStats.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="testimonials-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ stats.totalReviews }}+</div>
|
||||
<div class="stat-label">{{ labels.clients }}</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ stats.averageRating }}/5</div>
|
||||
<div class="stat-label">{{ labels.rating }}</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ stats.projectsCompleted }}+</div>
|
||||
<div class="stat-label">{{ labels.projects }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Stats {
|
||||
totalReviews: number
|
||||
averageRating: number
|
||||
projectsCompleted: number
|
||||
}
|
||||
|
||||
interface Labels {
|
||||
clients: string
|
||||
rating: string
|
||||
projects: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
stats: Stats
|
||||
labels: Labels
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import '@/components/styles/TestimonialsStats.css';
|
||||
</style>
|
Reference in New Issue
Block a user