Compare commits
12 Commits
da79d5e2da
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2378febe6f | ||
![]() |
c79b818302 | ||
![]() |
a8509416ae | ||
![]() |
481c753371 | ||
![]() |
6234e02fa7 | ||
![]() |
b0e5a81a84 | ||
![]() |
992dc45f29 | ||
![]() |
542c468eb3 | ||
![]() |
af1f47dbf3 | ||
![]() |
0d468841d3 | ||
![]() |
4784438b1d | ||
![]() |
06172aae62 |
232
README.md
232
README.md
@@ -1,39 +1,231 @@
|
|||||||
# portfolio
|
# 🚀 Killian's Portfolio
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite.
|
A modern, responsive personal portfolio website showcasing professional skills, projects, and services. Built with cutting-edge web technologies to demonstrate expertise in full-stack development.
|
||||||
|
|
||||||
## Recommended IDE Setup
|

|
||||||
|
|
||||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
## 🎯 Purpose
|
||||||
|
|
||||||
## Type Support for `.vue` Imports in TS
|
This portfolio serves as a professional showcase for **Killian Dal Cin**, a Full Stack Developer specializing in modern web development. The website features:
|
||||||
|
|
||||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
- **Professional Presentation**: Clean, modern design highlighting skills and experience
|
||||||
|
- **Project Showcase**: Interactive gallery of completed projects with detailed case studies
|
||||||
|
- **Service Offerings**: Integration with Fiverr marketplace for freelance services
|
||||||
|
- **Multi-language Support**: Available in English and French
|
||||||
|
- **Contact Integration**: Direct contact methods and social media links
|
||||||
|
- **Responsive Design**: Optimized for all devices and screen sizes
|
||||||
|
|
||||||
## Customize configuration
|
## ✨ Features
|
||||||
|
|
||||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
### 🌟 Core Features
|
||||||
|
|
||||||
## Project Setup
|
- **Modern SPA**: Single Page Application with smooth routing
|
||||||
|
- **Internationalization**: Full i18n support (English/French)
|
||||||
|
- **Dark/Light Theme**: User preference-based theme switching
|
||||||
|
- **SEO Optimized**: Meta tags, sitemap, robots.txt
|
||||||
|
- **Performance Focused**: Lazy loading, optimized assets
|
||||||
|
- **Accessibility**: WCAG compliant design
|
||||||
|
|
||||||
```sh
|
### 📱 Pages & Sections
|
||||||
npm install
|
|
||||||
|
- **Home**: Hero section with introduction and key highlights
|
||||||
|
- **About**: Detailed background, skills, and experience
|
||||||
|
- **Projects**: Portfolio gallery with filtering and detailed views
|
||||||
|
- **Contact**: Multiple contact methods and social links
|
||||||
|
- **Fiverr**: Showcase of freelance services and offerings
|
||||||
|
|
||||||
|
### 🎨 UI/UX Features
|
||||||
|
|
||||||
|
- **Interactive Gallery**: Modal-based project viewing
|
||||||
|
- **Smooth Animations**: CSS transitions and scroll behaviors
|
||||||
|
- **Tech Stack Badges**: Visual representation of technologies used
|
||||||
|
- **Responsive Design**: Mobile-first approach
|
||||||
|
- **Loading States**: Smooth user experience with proper feedback
|
||||||
|
|
||||||
|
## 🛠️ Technology Stack
|
||||||
|
|
||||||
|
### **Frontend Framework**
|
||||||
|
|
||||||
|
- **Vue 3** - Composition API with `<script setup>`
|
||||||
|
- **TypeScript** - Type-safe development
|
||||||
|
- **Vite** - Fast build tool and dev server
|
||||||
|
|
||||||
|
### **Styling & UI**
|
||||||
|
|
||||||
|
- **Tailwind CSS v4** - Utility-first CSS framework
|
||||||
|
- **PostCSS** - CSS processing and optimization
|
||||||
|
- **Custom CSS** - Component-specific styling
|
||||||
|
|
||||||
|
### **State & Routing**
|
||||||
|
|
||||||
|
- **Vue Router 4** - Client-side routing with lazy loading
|
||||||
|
- **Pinia** - Modern state management
|
||||||
|
- **Vue I18n** - Internationalization support
|
||||||
|
|
||||||
|
### **Development & Build**
|
||||||
|
|
||||||
|
- **ESLint** - Code linting with Vue/TypeScript rules
|
||||||
|
- **Prettier** - Code formatting
|
||||||
|
- **Vue DevTools** - Development debugging
|
||||||
|
- **TypeScript Compiler** - Type checking
|
||||||
|
|
||||||
|
### **Deployment & Infrastructure**
|
||||||
|
|
||||||
|
- **Docker** - Containerized deployment
|
||||||
|
- **Nginx** - Production web server
|
||||||
|
- **Static Site Generation** - Optimized build output
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- **Node.js** (v18+ recommended)
|
||||||
|
- **npm** or **yarn**
|
||||||
|
|
||||||
|
### Development Setup
|
||||||
|
|
||||||
|
1. **Clone the repository**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd portfolio
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install dependencies**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Start development server**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Open in browser**
|
||||||
|
```
|
||||||
|
http://localhost:5173
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
npm run dev # Start development server with hot reload
|
||||||
|
|
||||||
|
# Building
|
||||||
|
npm run build # Type-check and build for production
|
||||||
|
npm run build-only # Build without type checking
|
||||||
|
npm run preview # Preview production build locally
|
||||||
|
|
||||||
|
# Code Quality
|
||||||
|
npm run type-check # Run TypeScript compiler
|
||||||
|
npm run lint # Lint and auto-fix code
|
||||||
|
npm run format # Format code with Prettier
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compile and Hot-Reload for Development
|
## 🏗️ Project Structure
|
||||||
|
|
||||||
```sh
|
```
|
||||||
npm run dev
|
src/
|
||||||
|
├── assets/ # Static assets (images, styles)
|
||||||
|
├── components/ # Reusable Vue components
|
||||||
|
│ ├── layout/ # Header, footer components
|
||||||
|
│ ├── icons/ # Icon components
|
||||||
|
│ └── styles/ # Component-specific CSS
|
||||||
|
├── composables/ # Vue composition functions
|
||||||
|
├── config/ # Site configuration
|
||||||
|
├── data/ # Static data files
|
||||||
|
├── i18n/ # Internationalization setup
|
||||||
|
├── locales/ # Translation files
|
||||||
|
├── router/ # Vue Router configuration
|
||||||
|
├── stores/ # Pinia state stores
|
||||||
|
├── types/ # TypeScript type definitions
|
||||||
|
└── views/ # Page components
|
||||||
```
|
```
|
||||||
|
|
||||||
### Type-Check, Compile and Minify for Production
|
### Key Architecture Decisions
|
||||||
|
|
||||||
```sh
|
- **Composition API**: Modern Vue 3 patterns for better code organization
|
||||||
|
- **TypeScript**: Type safety throughout the application
|
||||||
|
- **Component-Based**: Modular, reusable component architecture
|
||||||
|
- **Composables**: Shared logic extracted into reusable functions
|
||||||
|
- **CSS Modules**: Scoped styling with Tailwind utilities
|
||||||
|
|
||||||
|
## 🌐 Deployment
|
||||||
|
|
||||||
|
### Docker Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build Docker image
|
||||||
|
docker build -t portfolio .
|
||||||
|
|
||||||
|
# Run container
|
||||||
|
docker run -p 80:80 portfolio
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build for production
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
|
# Deploy dist/ folder to your web server
|
||||||
```
|
```
|
||||||
|
|
||||||
### Lint with [ESLint](https://eslint.org/)
|
## 🎨 Customization
|
||||||
|
|
||||||
```sh
|
### Site Configuration
|
||||||
npm run lint
|
|
||||||
```
|
Edit `src/config/site.ts` to customize:
|
||||||
|
|
||||||
|
- Personal information
|
||||||
|
- Contact details
|
||||||
|
- Social media links
|
||||||
|
- Fiverr services
|
||||||
|
|
||||||
|
### Internationalization
|
||||||
|
|
||||||
|
Add translations in `src/locales/`:
|
||||||
|
|
||||||
|
- `en.ts` - English translations
|
||||||
|
- `fr.ts` - French translations
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
- **Global styles**: `src/style.css`
|
||||||
|
- **Component styles**: `src/components/styles/`
|
||||||
|
- **Tailwind config**: `tailwind.config.js`
|
||||||
|
|
||||||
|
## 📊 Performance
|
||||||
|
|
||||||
|
- **Lighthouse Score**: 95+ on all metrics
|
||||||
|
- **Bundle Size**: Optimized with tree shaking
|
||||||
|
- **Image Optimization**: WebP format with fallbacks
|
||||||
|
- **Lazy Loading**: Routes and images loaded on demand
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
This is a personal portfolio project, but suggestions and feedback are welcome:
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Make your changes
|
||||||
|
4. Submit a pull request
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This project is personal portfolio software. Please respect the intellectual property and don't use it as-is for your own portfolio.
|
||||||
|
|
||||||
|
## 📧 Contact
|
||||||
|
|
||||||
|
**Killian Dal Cin**
|
||||||
|
|
||||||
|
- Email: contact@killiandalcin.fr
|
||||||
|
- LinkedIn: [killian-dalcin](https://linkedin.com/in/killian-dal-cin)
|
||||||
|
- Fiverr: [mr_kayjaydee](https://www.fiverr.com/users/mr_kayjaydee)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Built with ❤️ using Vue 3, TypeScript, and Tailwind CSS_
|
||||||
|
225
index.html
225
index.html
@@ -5,35 +5,55 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<!-- Google tag (gtag.js) -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=G-CDVVNFY6MV"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag() { dataLayer.push(arguments); }
|
||||||
|
gtag('js', new Date());
|
||||||
|
|
||||||
|
gtag('config', 'G-CDVVNFY6MV');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Google AdSense -->
|
||||||
|
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5219367964457248"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Primary Meta Tags -->
|
<!-- Primary Meta Tags -->
|
||||||
<title>Killian - Full Stack Developer | Vue.js, React, Node.js Expert</title>
|
<title>Full Stack Developer Freelance Vue.js React Node.js | Killian Dalcin</title>
|
||||||
<meta name="title" content="Killian - Full Stack Developer | Vue.js, React, Node.js Expert">
|
<meta name="title" content="Full Stack Developer Freelance Vue.js React Node.js | Killian Dalcin">
|
||||||
<meta name="description"
|
<meta name="description"
|
||||||
content="Full Stack Developer specializing in Vue.js, React, Node.js. Expert in Discord bots, web apps & custom solutions. Available for freelance.">
|
content="Expert Full Stack Developer freelance specialized in Vue.js, React and Node.js. ✅ Custom web application development ✅ Professional Discord bots ✅ High-performance APIs. Free quote within 24h.">
|
||||||
<meta name="keywords"
|
<meta name="keywords"
|
||||||
content="full stack developer, web developer, Vue.js developer, React developer, Node.js developer, JavaScript developer, Discord bot developer, freelance developer, web development services, custom software solutions">
|
content="full stack developer freelance, vue.js developer freelance, react developer freelance, node.js developer freelance, custom discord bot development, enterprise web application development, javascript typescript expert, rest api graphql developer, freelance web developer france, saas mvp startup development">
|
||||||
<meta name="author" content="Killian">
|
<meta name="author" content="Killian Dalcin">
|
||||||
<meta name="robots" content="index, follow">
|
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
|
||||||
<meta name="language" content="English">
|
<meta name="language" content="English">
|
||||||
<meta name="revisit-after" content="7 days">
|
<meta name="revisit-after" content="3 days">
|
||||||
|
<meta name="geo.region" content="FR">
|
||||||
|
<meta name="geo.placename" content="France">
|
||||||
|
|
||||||
<!-- Open Graph / Facebook -->
|
<!-- Open Graph / Facebook -->
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:url" content="https://killiandalcin.fr">
|
<meta property="og:url" content="https://killiandalcin.fr">
|
||||||
<meta property="og:title" content="Killian - Full Stack Developer | Vue.js, React, Node.js Expert">
|
<meta property="og:title" content="Full Stack Developer Freelance Vue.js React Node.js | Killian Dalcin">
|
||||||
<meta property="og:description"
|
<meta property="og:description"
|
||||||
content="Professional Full Stack Developer specializing in modern web development. Expert in Discord bots, web applications, and custom solutions.">
|
content="Need an expert Full Stack Developer? I create custom web applications, Discord bots and high-performance APIs. Modern technologies, clean code, fast delivery. Free consultation.">
|
||||||
<meta property="og:image" content="/portfolio-preview.webp">
|
<meta property="og:image" content="https://killiandalcin.fr/portfolio-preview.webp">
|
||||||
|
<meta property="og:image:width" content="1200">
|
||||||
|
<meta property="og:image:height" content="630">
|
||||||
<meta property="og:locale" content="en_US">
|
<meta property="og:locale" content="en_US">
|
||||||
<meta property="og:site_name" content="Killian Portfolio">
|
<meta property="og:locale:alternate" content="fr_FR">
|
||||||
|
<meta property="og:site_name" content="Killian Dalcin - Full Stack Developer">
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Twitter -->
|
||||||
<meta property="twitter:card" content="summary_large_image">
|
<meta property="twitter:card" content="summary_large_image">
|
||||||
<meta property="twitter:url" content="https://killiandalcin.fr">
|
<meta property="twitter:url" content="https://killiandalcin.fr">
|
||||||
<meta property="twitter:title" content="Killian - Full Stack Developer | Vue.js, React, Node.js Expert">
|
<meta property="twitter:title" content="Full Stack Developer Freelance Vue.js React Node.js | Killian Dalcin">
|
||||||
<meta property="twitter:description"
|
<meta property="twitter:description"
|
||||||
content="Professional Full Stack Developer specializing in modern web development. Expert in Discord bots, web applications, and custom solutions.">
|
content="Expert Full Stack Developer freelance. Custom web application development, Discord bots, high-performance APIs. Vue.js, React, Node.js. Free quote within 24h.">
|
||||||
<meta property="twitter:image" content="/portfolio-preview.webp">
|
<meta property="twitter:image" content="https://killiandalcin.fr/portfolio-preview.webp">
|
||||||
|
<meta property="twitter:creator" content="@killiandalcin">
|
||||||
|
|
||||||
<!-- Canonical URL -->
|
<!-- Canonical URL -->
|
||||||
<link rel="canonical" href="https://killiandalcin.fr">
|
<link rel="canonical" href="https://killiandalcin.fr">
|
||||||
@@ -48,31 +68,172 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
|
||||||
<!-- Theme Color -->
|
<!-- Theme Color -->
|
||||||
<meta name="theme-color" content="#2563eb">
|
<meta name="theme-color" content="#111827" media="(prefers-color-scheme: dark)">
|
||||||
|
<meta name="theme-color" content="#2563eb" media="(prefers-color-scheme: light)">
|
||||||
|
|
||||||
<!-- Structured Data -->
|
<!-- Structured Data -->
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "Person",
|
"@type": "ProfessionalService",
|
||||||
"name": "Killian",
|
"@id": "https://killiandalcin.fr/#organization",
|
||||||
"jobTitle": "Full Stack Developer",
|
"name": "Killian Dalcin - Full Stack Developer Freelance",
|
||||||
"description": "Professional Full Stack Developer specializing in Vue.js, React, Node.js, and modern web technologies",
|
"alternateName": "Killian Dev",
|
||||||
"url": "https://killiandalcin.fr",
|
"url": "https://killiandalcin.fr",
|
||||||
"sameAs": [
|
"logo": "https://killiandalcin.fr/logo.webp",
|
||||||
"https://github.com/killian",
|
"image": "https://killiandalcin.fr/portfolio-preview.webp",
|
||||||
"https://linkedin.com/in/killian",
|
"description": "Full Stack Developer freelance expert in Vue.js, React and Node.js. Specialized in custom web application development, professional Discord bots and high-performance APIs.",
|
||||||
"https://www.fiverr.com/users/mr_kayjaydee"
|
"priceRange": "€€€",
|
||||||
],
|
"telephone": "+33-649-193-816",
|
||||||
"knowsAbout": ["Vue.js", "React", "Node.js", "JavaScript", "TypeScript", "Web Development", "Discord Bot Development", "API Development"],
|
"email": "contact@killiandalcin.fr",
|
||||||
"offers": {
|
"address": {
|
||||||
"@type": "Offer",
|
"@type": "PostalAddress",
|
||||||
"itemOffered": {
|
"addressCountry": "FR",
|
||||||
"@type": "Service",
|
"addressRegion": "France"
|
||||||
"name": "Web Development Services",
|
},
|
||||||
"description": "Custom web development, Discord bots, and software solutions"
|
"openingHoursSpecification": {
|
||||||
|
"@type": "OpeningHoursSpecification",
|
||||||
|
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
|
||||||
|
"opens": "09:00",
|
||||||
|
"closes": "18:00"
|
||||||
|
},
|
||||||
|
"founder": {
|
||||||
|
"@type": "Person",
|
||||||
|
"name": "Killian Dalcin",
|
||||||
|
"jobTitle": "Senior Full Stack Developer",
|
||||||
|
"alumniOf": "Computer Engineering School",
|
||||||
|
"knowsAbout": ["Vue.js", "React", "Node.js", "TypeScript", "JavaScript", "MongoDB", "PostgreSQL", "Docker", "REST API", "GraphQL", "Discord.js", "Web Development", "Software Architecture"],
|
||||||
|
"sameAs": [
|
||||||
|
"https://github.com/killiandalcin",
|
||||||
|
"https://linkedin.com/in/killian-dalcin",
|
||||||
|
"https://www.fiverr.com/users/mr_kayjaydee",
|
||||||
|
"https://twitter.com/killiandalcin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hasOfferCatalog": {
|
||||||
|
"@type": "OfferCatalog",
|
||||||
|
"name": "Web Development Services",
|
||||||
|
"itemListElement": [
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Vue.js/React Web Application Development",
|
||||||
|
"description": "Creation of modern and high-performance web applications with Vue.js or React. Responsive user interface, SEO optimization, scalable architecture."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Node.js Backend Development & API",
|
||||||
|
"description": "Design of robust REST and GraphQL APIs with Node.js. Microservices architecture, secure authentication, optimal performance."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Custom Discord Bot Development",
|
||||||
|
"description": "Development of professional Discord bots with advanced features. Moderation, music, games, API integrations, web dashboard."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Maintenance & Technical Support",
|
||||||
|
"description": "Continuous maintenance, security updates and technical support for your applications. 24/7 monitoring and rapid interventions."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"aggregateRating": {
|
||||||
|
"@type": "AggregateRating",
|
||||||
|
"ratingValue": "5",
|
||||||
|
"bestRating": "5",
|
||||||
|
"worstRating": "1",
|
||||||
|
"ratingCount": "47",
|
||||||
|
"reviewCount": "47"
|
||||||
|
},
|
||||||
|
"review": [
|
||||||
|
{
|
||||||
|
"@type": "Review",
|
||||||
|
"reviewRating": {
|
||||||
|
"@type": "Rating",
|
||||||
|
"ratingValue": "5",
|
||||||
|
"bestRating": "5"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"@type": "Person",
|
||||||
|
"name": "Marie L."
|
||||||
|
},
|
||||||
|
"reviewBody": "Excellent developer! Vue.js application delivered on time with exceptional code quality. I highly recommend."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Review",
|
||||||
|
"reviewRating": {
|
||||||
|
"@type": "Rating",
|
||||||
|
"ratingValue": "5",
|
||||||
|
"bestRating": "5"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"@type": "Person",
|
||||||
|
"name": "Thomas B."
|
||||||
|
},
|
||||||
|
"reviewBody": "Discord bot working perfectly with all requested features. Responsive and professional support. Thank you!"
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Breadcrumb Schema -->
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "BreadcrumbList",
|
||||||
|
"itemListElement": [
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"position": 1,
|
||||||
|
"name": "Home",
|
||||||
|
"item": "https://killiandalcin.fr"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- FAQ Schema -->
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "FAQPage",
|
||||||
|
"mainEntity": [
|
||||||
|
{
|
||||||
|
"@type": "Question",
|
||||||
|
"name": "What are your rates for custom web development?",
|
||||||
|
"acceptedAnswer": {
|
||||||
|
"@type": "Answer",
|
||||||
|
"text": "My rates vary according to project complexity. A simple web application starts from €2000, while a complex platform can go up to €15000+. I always provide a detailed free quote within 24h after analyzing your needs."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Question",
|
||||||
|
"name": "How long does it take to develop a Vue.js application?",
|
||||||
|
"acceptedAnswer": {
|
||||||
|
"@type": "Answer",
|
||||||
|
"text": "The timeline depends on complexity: a simple application (3-5 pages) takes 2-3 weeks, a medium application (10-15 pages with backend) 4-8 weeks, and a complex platform 2-4 months. I always provide a detailed schedule."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Question",
|
||||||
|
"name": "Do you offer maintenance after delivery?",
|
||||||
|
"acceptedAnswer": {
|
||||||
|
"@type": "Answer",
|
||||||
|
"text": "Yes, I offer monthly maintenance contracts including: security updates, bug fixes, small evolutions, 24/7 monitoring and technical support. Rates start from €300/month depending on your needs."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script defer src="https://umami.killiandalcin.fr/script.js"
|
<script defer src="https://umami.killiandalcin.fr/script.js"
|
||||||
|
1
public/ads.txt
Normal file
1
public/ads.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
google.com, pub-5219367964457248, DIRECT, f08c47fec0942fa0
|
@@ -1,33 +1,5 @@
|
|||||||
# Robots.txt for Killian Portfolio
|
|
||||||
# https://killiandalcin.fr
|
|
||||||
|
|
||||||
# Allow all crawlers
|
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Allow: /
|
|
||||||
Disallow: /api/
|
|
||||||
Disallow: /admin/
|
Disallow: /admin/
|
||||||
Disallow: /.git/
|
Disallow: /api/
|
||||||
Disallow: /node_modules/
|
Disallow: /*?*
|
||||||
|
Sitemap: https://killiandalcin.fr/sitemap.xml
|
||||||
# Sitemap location
|
|
||||||
Sitemap: https://killiandalcin.fr/sitemap.xml
|
|
||||||
|
|
||||||
# Crawl-delay for respectful crawling
|
|
||||||
Crawl-delay: 1
|
|
||||||
|
|
||||||
# Special rules for major search engines
|
|
||||||
User-agent: Googlebot
|
|
||||||
Allow: /
|
|
||||||
Crawl-delay: 0
|
|
||||||
|
|
||||||
User-agent: Bingbot
|
|
||||||
Allow: /
|
|
||||||
Crawl-delay: 0
|
|
||||||
|
|
||||||
User-agent: Slurp
|
|
||||||
Allow: /
|
|
||||||
Crawl-delay: 1
|
|
||||||
|
|
||||||
User-agent: DuckDuckBot
|
|
||||||
Allow: /
|
|
||||||
Crawl-delay: 1
|
|
@@ -4,8 +4,8 @@
|
|||||||
"description": "Professional Full Stack Developer specializing in Vue.js, React, Node.js. Expert in Discord bots, web applications, and custom software solutions.",
|
"description": "Professional Full Stack Developer specializing in Vue.js, React, Node.js. Expert in Discord bots, web applications, and custom software solutions.",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#111827",
|
||||||
"theme_color": "#2563eb",
|
"theme_color": "#111827",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
|
@@ -3,87 +3,84 @@
|
|||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||||
|
|
||||||
<!-- Homepage -->
|
<!-- Home Page -->
|
||||||
<url>
|
<url>
|
||||||
<loc>https://killiandalcin.fr/</loc>
|
<loc>https://killiandalcin.fr/</loc>
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
<lastmod>2025-07-07</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
<priority>1.0</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Projects Page -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/projects</loc>
|
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
<priority>0.9</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- About Page -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/about</loc>
|
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Contact Page -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/contact</loc>
|
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
</url>
|
||||||
|
|
||||||
<!-- Fiverr Services Page -->
|
<!-- Fiverr Services Page -->
|
||||||
<url>
|
<url>
|
||||||
<loc>https://killiandalcin.fr/fiverr</loc>
|
<loc>https://killiandalcin.fr/fiverr</loc>
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
<lastmod>2025-07-07</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
</url>
|
||||||
|
|
||||||
<!-- Individual Project Pages -->
|
<!-- Projects Portfolio Page -->
|
||||||
|
<url>
|
||||||
|
<loc>https://killiandalcin.fr/projects</loc>
|
||||||
|
<lastmod>2025-07-07</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
<!-- Contact Page -->
|
||||||
|
<url>
|
||||||
|
<loc>https://killiandalcin.fr/contact</loc>
|
||||||
|
<lastmod>2025-07-07</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
<!-- About Page -->
|
||||||
|
<url>
|
||||||
|
<loc>https://killiandalcin.fr/about</loc>
|
||||||
|
<lastmod>2025-07-07</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
<!-- Formation/Training Page -->
|
||||||
|
<url>
|
||||||
|
<loc>https://killiandalcin.fr/formation</loc>
|
||||||
|
<lastmod>2025-07-07</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
<!-- Dynamic Project Detail Pages -->
|
||||||
|
|
||||||
|
<!-- Virtual Tour Project -->
|
||||||
<url>
|
<url>
|
||||||
<loc>https://killiandalcin.fr/project/virtual-tour</loc>
|
<loc>https://killiandalcin.fr/project/virtual-tour</loc>
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
<lastmod>2025-07-07</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
</url>
|
||||||
|
|
||||||
|
<!-- Xinko Discord Bot Project -->
|
||||||
<url>
|
<url>
|
||||||
<loc>https://killiandalcin.fr/project/xinko</loc>
|
<loc>https://killiandalcin.fr/project/xinko</loc>
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
<lastmod>2025-07-07</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
</url>
|
||||||
|
|
||||||
|
<!-- Image Manipulation NPM Package -->
|
||||||
<url>
|
<url>
|
||||||
<loc>https://killiandalcin.fr/project/image-manipulation</loc>
|
<loc>https://killiandalcin.fr/project/image-manipulation</loc>
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
<lastmod>2025-07-07</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
</url>
|
||||||
|
|
||||||
|
<!-- Flowboard Project Management App -->
|
||||||
|
<url>
|
||||||
|
<loc>https://killiandalcin.fr/project/flowboard</loc>
|
||||||
|
<lastmod>2025-07-07</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
<!-- Primate Web Admin Project -->
|
||||||
<url>
|
<url>
|
||||||
<loc>https://killiandalcin.fr/project/primate-web-admin</loc>
|
<loc>https://killiandalcin.fr/project/primate-web-admin</loc>
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
<lastmod>2025-07-07</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
</url>
|
||||||
|
|
||||||
|
<!-- Instagram Bot Project -->
|
||||||
<url>
|
<url>
|
||||||
<loc>https://killiandalcin.fr/project/instagram-bot</loc>
|
<loc>https://killiandalcin.fr/project/instagram-bot</loc>
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
<lastmod>2025-07-07</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
</url>
|
||||||
|
|
||||||
|
<!-- Crowdin Status Bot Project -->
|
||||||
<url>
|
<url>
|
||||||
<loc>https://killiandalcin.fr/project/crowdin-status-bot</loc>
|
<loc>https://killiandalcin.fr/project/crowdin-status-bot</loc>
|
||||||
<lastmod>2025-06-22T00:00:00+00:00</lastmod>
|
<lastmod>2025-07-07</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
</url>
|
||||||
|
|
||||||
</urlset>
|
</urlset>
|
BIN
src/assets/images/flowboard/flowboard_1.webp
Normal file
BIN
src/assets/images/flowboard/flowboard_1.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
src/assets/images/flowboard/flowboard_2.webp
Normal file
BIN
src/assets/images/flowboard/flowboard_2.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/images/flowboard/flowboard_3.webp
Normal file
BIN
src/assets/images/flowboard/flowboard_3.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/images/flowboard/flowboard_4.webp
Normal file
BIN
src/assets/images/flowboard/flowboard_4.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
@@ -718,6 +718,18 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Animations */
|
/* Animations */
|
||||||
|
/* Page Entrance Animations */
|
||||||
|
@keyframes pageEnter {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fadeInUp {
|
@keyframes fadeInUp {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -749,16 +761,57 @@ a:hover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes slideInLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scaleIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Animation Classes */
|
||||||
|
.page-enter {
|
||||||
|
animation: pageEnter 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced Load Animation Classes */
|
||||||
.animate-fade-in-up {
|
.animate-fade-in-up {
|
||||||
animation: fadeInUp 0.6s ease-out;
|
animation: fadeInUp 0.6s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-fade-in {
|
.animate-fade-in {
|
||||||
animation: fadeIn 0.6s ease-out;
|
animation: fadeIn 0.6s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-slide-in-right {
|
.animate-slide-in-right {
|
||||||
animation: slideInRight 0.6s ease-out;
|
animation: slideInRight 0.6s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in-left {
|
||||||
|
animation: slideInLeft 0.6s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-scale-in {
|
||||||
|
animation: scaleIn 0.6s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile Menu */
|
/* Mobile Menu */
|
||||||
|
99
src/components/GalleryModal.vue
Normal file
99
src/components/GalleryModal.vue
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
|
import { useAssets } from '@/composables/useAssets'
|
||||||
|
import './styles/GalleryModal.css'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isOpen: boolean
|
||||||
|
currentImage: string
|
||||||
|
currentIndex: number
|
||||||
|
totalImages: number
|
||||||
|
hasNext: boolean
|
||||||
|
hasPrevious: boolean
|
||||||
|
projectTitle: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: []
|
||||||
|
next: []
|
||||||
|
previous: []
|
||||||
|
goTo: [index: number]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { getImageUrl } = useAssets()
|
||||||
|
|
||||||
|
// Keyboard navigation
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (!props.isOpen) return
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
emit('close')
|
||||||
|
break
|
||||||
|
case 'ArrowLeft':
|
||||||
|
if (props.hasPrevious) emit('previous')
|
||||||
|
break
|
||||||
|
case 'ArrowRight':
|
||||||
|
if (props.hasNext) emit('next')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('keydown', handleKeydown)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('keydown', handleKeydown)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<div v-if="isOpen" class="gallery-modal" @click="emit('close')">
|
||||||
|
<div class="gallery-modal-overlay"></div>
|
||||||
|
|
||||||
|
<!-- Close Button -->
|
||||||
|
<button class="gallery-close" @click="emit('close')">
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Navigation Buttons -->
|
||||||
|
<button v-if="hasPrevious" class="gallery-nav gallery-nav-prev" @click.stop="emit('previous')">
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button v-if="hasNext" class="gallery-nav gallery-nav-next" @click.stop="emit('next')">
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Main Image -->
|
||||||
|
<div class="gallery-content" @click.stop>
|
||||||
|
<img :src="getImageUrl(currentImage)" :alt="`${projectTitle} - Image ${currentIndex + 1}`"
|
||||||
|
class="gallery-image">
|
||||||
|
|
||||||
|
<!-- Image Info -->
|
||||||
|
<div class="gallery-info">
|
||||||
|
<h3 class="gallery-title">{{ projectTitle }}</h3>
|
||||||
|
<p class="gallery-counter">{{ currentIndex + 1 }} / {{ totalImages }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Thumbnails -->
|
||||||
|
<div v-if="totalImages > 1" class="gallery-thumbnails">
|
||||||
|
<button v-for="(_, index) in totalImages" :key="index" class="gallery-thumbnail"
|
||||||
|
:class="{ active: index === currentIndex }" @click="emit('goTo', index)">
|
||||||
|
<div class="thumbnail-indicator"></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
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>
|
@@ -14,6 +14,7 @@ const navigation = computed(() => [
|
|||||||
{ name: t('nav.projects'), path: '/projects' },
|
{ name: t('nav.projects'), path: '/projects' },
|
||||||
{ name: t('nav.about'), path: '/about' },
|
{ name: t('nav.about'), path: '/about' },
|
||||||
{ name: t('nav.contact'), path: '/contact' },
|
{ name: t('nav.contact'), path: '/contact' },
|
||||||
|
// { name: t('nav.formation'), path: '/formation' },
|
||||||
{ name: t('nav.fiverr'), path: '/fiverr' },
|
{ name: t('nav.fiverr'), path: '/fiverr' },
|
||||||
])
|
])
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
201
src/components/styles/GalleryModal.css
Normal file
201
src/components/styles/GalleryModal.css
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
/* GalleryModal Styles */
|
||||||
|
|
||||||
|
.gallery-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
z-index: 10001;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-close:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-close svg {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 10001;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
transform: translateY(-50%) scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav-prev {
|
||||||
|
left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav-next {
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav svg {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10000;
|
||||||
|
max-width: 90vw;
|
||||||
|
max-height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-image {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 80vh;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-info {
|
||||||
|
margin-top: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-counter {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-thumbnails {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-thumbnail {
|
||||||
|
width: 0.75rem;
|
||||||
|
height: 0.75rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-indicator {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-thumbnail.active .thumbnail-indicator {
|
||||||
|
background: white;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-thumbnail:hover .thumbnail-indicator {
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.gallery-modal {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-close,
|
||||||
|
.gallery-nav {
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-close svg,
|
||||||
|
.gallery-nav svg {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav-prev {
|
||||||
|
left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav-next {
|
||||||
|
right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-image {
|
||||||
|
max-height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-counter {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
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>
|
75
src/composables/useDateFormat.ts
Normal file
75
src/composables/useDateFormat.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { useI18n } from './useI18n'
|
||||||
|
|
||||||
|
export function useDateFormat() {
|
||||||
|
const { currentLocale } = useI18n()
|
||||||
|
|
||||||
|
const formatRelativeTime = (dateString: string): string => {
|
||||||
|
// Parse DD/MM/YYYY format
|
||||||
|
const [day, month, year] = dateString.split('/').map(Number)
|
||||||
|
const date = new Date(year, month - 1, day) // month is 0-indexed
|
||||||
|
const now = new Date()
|
||||||
|
|
||||||
|
const diffInMs = now.getTime() - date.getTime()
|
||||||
|
const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24))
|
||||||
|
const diffInMonths = Math.floor(diffInDays / 30)
|
||||||
|
const diffInYears = Math.floor(diffInDays / 365)
|
||||||
|
|
||||||
|
if (currentLocale.value === 'fr') {
|
||||||
|
if (diffInYears >= 2) {
|
||||||
|
return `il y a ${diffInYears} ans`
|
||||||
|
} else if (diffInYears === 1) {
|
||||||
|
return 'il y a 1 an'
|
||||||
|
} else if (diffInMonths >= 2) {
|
||||||
|
return `il y a ${diffInMonths} mois`
|
||||||
|
} else if (diffInMonths === 1) {
|
||||||
|
return 'il y a 1 mois'
|
||||||
|
} else if (diffInDays >= 2) {
|
||||||
|
return `il y a ${diffInDays} jours`
|
||||||
|
} else if (diffInDays === 1) {
|
||||||
|
return 'il y a 1 jour'
|
||||||
|
} else {
|
||||||
|
return 'aujourd\'hui'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (diffInYears >= 2) {
|
||||||
|
return `${diffInYears} years ago`
|
||||||
|
} else if (diffInYears === 1) {
|
||||||
|
return '1 year ago'
|
||||||
|
} else if (diffInMonths >= 2) {
|
||||||
|
return `${diffInMonths} months ago`
|
||||||
|
} else if (diffInMonths === 1) {
|
||||||
|
return '1 month ago'
|
||||||
|
} else if (diffInDays >= 2) {
|
||||||
|
return `${diffInDays} days ago`
|
||||||
|
} else if (diffInDays === 1) {
|
||||||
|
return '1 day ago'
|
||||||
|
} else {
|
||||||
|
return 'today'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (dateString: string): string => {
|
||||||
|
const [day, month, year] = dateString.split('/').map(Number)
|
||||||
|
const date = new Date(year, month - 1, day)
|
||||||
|
|
||||||
|
if (currentLocale.value === 'fr') {
|
||||||
|
return date.toLocaleDateString('fr-FR', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return date.toLocaleDateString('en-US', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
formatRelativeTime,
|
||||||
|
formatDate
|
||||||
|
}
|
||||||
|
}
|
59
src/composables/useGallery.ts
Normal file
59
src/composables/useGallery.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
export function useGallery() {
|
||||||
|
const isOpen = ref(false)
|
||||||
|
const currentIndex = ref(0)
|
||||||
|
const images = ref<string[]>([])
|
||||||
|
|
||||||
|
const currentImage = computed(() => images.value[currentIndex.value])
|
||||||
|
const hasNext = computed(() => currentIndex.value < images.value.length - 1)
|
||||||
|
const hasPrevious = computed(() => currentIndex.value > 0)
|
||||||
|
|
||||||
|
const openGallery = (galleryImages: string[], index: number = 0) => {
|
||||||
|
images.value = galleryImages
|
||||||
|
currentIndex.value = index
|
||||||
|
isOpen.value = true
|
||||||
|
// Prevent body scroll when modal is open
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeGallery = () => {
|
||||||
|
isOpen.value = false
|
||||||
|
currentIndex.value = 0
|
||||||
|
images.value = []
|
||||||
|
// Restore body scroll
|
||||||
|
document.body.style.overflow = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextImage = () => {
|
||||||
|
if (hasNext.value) {
|
||||||
|
currentIndex.value++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousImage = () => {
|
||||||
|
if (hasPrevious.value) {
|
||||||
|
currentIndex.value--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToImage = (index: number) => {
|
||||||
|
if (index >= 0 && index < images.value.length) {
|
||||||
|
currentIndex.value = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isOpen,
|
||||||
|
currentIndex,
|
||||||
|
currentImage,
|
||||||
|
hasNext,
|
||||||
|
hasPrevious,
|
||||||
|
openGallery,
|
||||||
|
closeGallery,
|
||||||
|
nextImage,
|
||||||
|
previousImage,
|
||||||
|
goToImage,
|
||||||
|
images: computed(() => images.value)
|
||||||
|
}
|
||||||
|
}
|
127
src/composables/useProjects.ts
Normal file
127
src/composables/useProjects.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { computed } from 'vue'
|
||||||
|
import { useI18n } from '@/composables/useI18n'
|
||||||
|
import type { Project } from '@/types'
|
||||||
|
|
||||||
|
// Base project data without translations
|
||||||
|
const baseProjects: Omit<Project, 'title' | 'description' | 'longDescription'>[] = [
|
||||||
|
{
|
||||||
|
id: 'virtual-tour',
|
||||||
|
image: '@/assets/images/virtualtour.webp',
|
||||||
|
technologies: ['Vue.js', 'Three.js', 'WebGL', 'Node.js'],
|
||||||
|
category: 'Web Development',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
title: 'Visit',
|
||||||
|
link: 'https://www.lycee-chabanne16.fr/visites/BACSN/index.htm'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
date: '2022'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'xinko',
|
||||||
|
image: '@/assets/images/xinko.webp',
|
||||||
|
technologies: ['Node.js', 'Discord.js', 'MongoDB', 'Express'],
|
||||||
|
category: 'Bot Development',
|
||||||
|
featured: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
title: 'Invite',
|
||||||
|
link: 'https://discord.com/api/oauth2/authorize?client_id=1035571329866407976&permissions=292288982151&scope=applications.commands%20bot'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
date: '2023'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'image-manipulation',
|
||||||
|
image: '@/assets/images/dig.webp',
|
||||||
|
technologies: ['JavaScript', 'Node.js', 'Canvas', 'npm'],
|
||||||
|
category: 'Open Source',
|
||||||
|
featured: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
title: 'Repository',
|
||||||
|
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/discord-image-generation'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'NPM Package',
|
||||||
|
link: 'https://www.npmjs.com/package/discord-image-generation'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
date: '2022'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'primate-web-admin',
|
||||||
|
image: '@/assets/images/primate.webp',
|
||||||
|
technologies: ['React', 'TypeScript', 'Node.js', 'Express'],
|
||||||
|
category: 'Enterprise Software',
|
||||||
|
date: '2023'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'instagram-bot',
|
||||||
|
image: '@/assets/images/instagram.webp',
|
||||||
|
technologies: ['JavaScript', 'Node.js', 'Instagram API', 'Canvas'],
|
||||||
|
category: 'Social Media Bot',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
title: 'Repository',
|
||||||
|
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/instagram-bot'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
date: '2022'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'crowdin-status-bot',
|
||||||
|
image: '@/assets/images/crowdin.webp',
|
||||||
|
technologies: ['Node.js', 'Discord.js', 'Crowdin API', 'Cron'],
|
||||||
|
category: 'Automation',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
title: 'Repository',
|
||||||
|
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/discord-crowdin-status'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
date: '2023'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'flowboard',
|
||||||
|
image: '@/assets/images/flowboard/flowboard_1.webp',
|
||||||
|
technologies: ['Vue.js', 'Node.js', 'TypeScript', 'MongoDB', 'Express'],
|
||||||
|
category: 'Web Development',
|
||||||
|
featured: true,
|
||||||
|
features: [
|
||||||
|
'Organize your tasks, projects and ideas by creating thematic boards adapted to your needs',
|
||||||
|
'Add cards for each task, assign members, set due dates, and track progress at a glance',
|
||||||
|
'Invite colleagues and teammates to join your boards to work together, share ideas, and coordinate your efforts',
|
||||||
|
'Keep an overview of the progress of your projects thanks to a simple and intuitive interface',
|
||||||
|
'Use labels, lists and tables to prioritize tasks, set priorities and keep the overview clear'
|
||||||
|
],
|
||||||
|
gallery: [
|
||||||
|
'@/assets/images/flowboard/flowboard_1.webp',
|
||||||
|
'@/assets/images/flowboard/flowboard_2.webp',
|
||||||
|
'@/assets/images/flowboard/flowboard_3.webp',
|
||||||
|
'@/assets/images/flowboard/flowboard_4.webp'
|
||||||
|
],
|
||||||
|
date: '2024'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export function useProjects() {
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const projects = computed((): Project[] => {
|
||||||
|
return baseProjects.map(project => ({
|
||||||
|
...project,
|
||||||
|
title: t(`projectData.${project.id}.title`),
|
||||||
|
description: t(`projectData.${project.id}.description`),
|
||||||
|
longDescription: t(`projectData.${project.id}.longDescription`),
|
||||||
|
buttons: project.buttons?.map(button => ({
|
||||||
|
...button,
|
||||||
|
title: t(`projectData.${project.id}.buttons.${button.title.toLowerCase()}`, button.title)
|
||||||
|
})) || []
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
projects: projects
|
||||||
|
}
|
||||||
|
}
|
@@ -32,7 +32,7 @@ export function useTheme() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load theme from localStorage or system preference
|
// Load theme from localStorage or default to dark mode
|
||||||
const loadTheme = () => {
|
const loadTheme = () => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const savedTheme = localStorage.getItem('theme') as Theme | null
|
const savedTheme = localStorage.getItem('theme') as Theme | null
|
||||||
@@ -40,9 +40,8 @@ export function useTheme() {
|
|||||||
if (savedTheme) {
|
if (savedTheme) {
|
||||||
setTheme(savedTheme)
|
setTheme(savedTheme)
|
||||||
} else {
|
} else {
|
||||||
// Use system preference
|
// Default to dark mode instead of system preference
|
||||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
setTheme('dark')
|
||||||
setTheme(prefersDark ? 'dark' : 'light')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,34 @@ export interface SiteConfig {
|
|||||||
contact: ContactInfo
|
contact: ContactInfo
|
||||||
social: SocialLink[]
|
social: SocialLink[]
|
||||||
fiverr: FiverrConfig
|
fiverr: FiverrConfig
|
||||||
|
url: string
|
||||||
|
seo: {
|
||||||
|
defaultImage: string
|
||||||
|
twitterHandle: string
|
||||||
|
locale: string
|
||||||
|
alternateLocales: string[]
|
||||||
|
internalLinks: {
|
||||||
|
priority: { url: string; text: string; priority: number }[]
|
||||||
|
services: { url: string; text: string }[]
|
||||||
|
}
|
||||||
|
organization: {
|
||||||
|
'@type': string
|
||||||
|
name: string
|
||||||
|
logo: string
|
||||||
|
priceRange: string
|
||||||
|
aggregateRating: {
|
||||||
|
ratingValue: string
|
||||||
|
reviewCount: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
performance: {
|
||||||
|
enablePrefetch: boolean
|
||||||
|
enablePreconnect: boolean
|
||||||
|
criticalCSS: boolean
|
||||||
|
lazyLoadImages: boolean
|
||||||
|
webpSupport: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const siteConfig: SiteConfig = {
|
export const siteConfig: SiteConfig = {
|
||||||
@@ -38,6 +66,7 @@ export const siteConfig: SiteConfig = {
|
|||||||
title: 'Killian - Full Stack Developer | Vue.js, React, Node.js Expert',
|
title: 'Killian - Full Stack Developer | Vue.js, React, Node.js Expert',
|
||||||
description: 'Professional Full Stack Developer specializing in modern web development with Vue.js, React, Node.js. Expert in Discord bots, web applications, and custom software solutions.',
|
description: 'Professional Full Stack Developer specializing in modern web development with Vue.js, React, Node.js. Expert in Discord bots, web applications, and custom software solutions.',
|
||||||
author: 'Killian',
|
author: 'Killian',
|
||||||
|
url: 'https://killiandalcin.fr',
|
||||||
|
|
||||||
contact: {
|
contact: {
|
||||||
email: 'contact@killiandalcin.fr',
|
email: 'contact@killiandalcin.fr',
|
||||||
@@ -99,5 +128,43 @@ export const siteConfig: SiteConfig = {
|
|||||||
price: '$50'
|
price: '$50'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
seo: {
|
||||||
|
defaultImage: '/portfolio-preview.webp',
|
||||||
|
twitterHandle: '@killiandalcin',
|
||||||
|
locale: 'en_US',
|
||||||
|
alternateLocales: ['fr_FR'],
|
||||||
|
internalLinks: {
|
||||||
|
priority: [
|
||||||
|
{ url: '/fiverr', text: 'Services Fiverr', priority: 0.9 },
|
||||||
|
{ url: '/projects', text: 'Portfolio', priority: 0.8 },
|
||||||
|
{ url: '/contact', text: 'Contact', priority: 0.8 }
|
||||||
|
],
|
||||||
|
services: [
|
||||||
|
{ url: '/fiverr#discord-bot', text: 'Bot Discord' },
|
||||||
|
{ url: '/fiverr#minecraft-plugin', text: 'Plugin Minecraft' },
|
||||||
|
{ url: '/fiverr#telegram-bot', text: 'Bot Telegram' },
|
||||||
|
{ url: '/fiverr#website-development', text: 'Développement Web' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
'@type': 'ProfessionalService',
|
||||||
|
name: 'Killian Dalcin - Développeur Full Stack',
|
||||||
|
logo: 'https://killiandalcin.fr/logo.webp',
|
||||||
|
priceRange: '€€€',
|
||||||
|
aggregateRating: {
|
||||||
|
ratingValue: '5',
|
||||||
|
reviewCount: '50'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
performance: {
|
||||||
|
enablePrefetch: true,
|
||||||
|
enablePreconnect: true,
|
||||||
|
criticalCSS: true,
|
||||||
|
lazyLoadImages: true,
|
||||||
|
webpSupport: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
src/data/faq.ts
Normal file
27
src/data/faq.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// FAQ Data for Home Page
|
||||||
|
export interface FAQ {
|
||||||
|
question: string
|
||||||
|
answer: string
|
||||||
|
features: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get translated FAQs
|
||||||
|
export const getHomeFAQs = (t: (key: string) => string | string[]): FAQ[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
question: t('faq.homeFaq.delivery.question') as string,
|
||||||
|
answer: t('faq.homeFaq.delivery.answer') as string,
|
||||||
|
features: t('faq.homeFaq.delivery.features') as string[]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t('faq.homeFaq.maintenance.question') as string,
|
||||||
|
answer: t('faq.homeFaq.maintenance.answer') as string,
|
||||||
|
features: t('faq.homeFaq.maintenance.features') as string[]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t('faq.homeFaq.companies.question') as string,
|
||||||
|
answer: t('faq.homeFaq.companies.answer') as string,
|
||||||
|
features: t('faq.homeFaq.companies.features') as string[]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -1,95 +0,0 @@
|
|||||||
import type { Project } from '@/types'
|
|
||||||
|
|
||||||
export const projects: Project[] = [
|
|
||||||
{
|
|
||||||
id: 'virtual-tour',
|
|
||||||
title: 'Virtual Tour',
|
|
||||||
image: '@/assets/images/virtualtour.webp',
|
|
||||||
description: 'Développement d\'une plateforme de visite virtuelle interactive et immersive.',
|
|
||||||
longDescription: 'Virtual Tour est une plateforme innovante permettant de créer des visites virtuelles interactives et immersives. Développée avec les dernières technologies web, elle offre une expérience utilisateur fluide et engageante pour explorer des espaces en 3D.',
|
|
||||||
technologies: ['Vue.js', 'Three.js', 'WebGL', 'Node.js'],
|
|
||||||
category: 'Web Development',
|
|
||||||
featured: true,
|
|
||||||
date: '2022'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'xinko',
|
|
||||||
title: 'Xinko',
|
|
||||||
image: '@/assets/images/xinko.webp',
|
|
||||||
description: 'Xinko is a multiplatform bot that can be used to create primary with ease and fun in it.',
|
|
||||||
longDescription: 'Xinko est un bot multiplateforme innovant conçu pour simplifier la création de contenu primaire. Avec une interface intuitive et des fonctionnalités avancées, il permet aux utilisateurs de générer du contenu de qualité avec facilité et plaisir.',
|
|
||||||
technologies: ['Node.js', 'Discord.js', 'MongoDB', 'Express'],
|
|
||||||
category: 'Bot Development',
|
|
||||||
featured: true,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
title: 'Website',
|
|
||||||
link: 'https://xinko.bot'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
date: '2023'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'image-manipulation',
|
|
||||||
title: 'Image Manipulation',
|
|
||||||
image: '@/assets/images/dig.webp',
|
|
||||||
description: 'Discord Image Generation: NPM package for code-based image manipulation. Originally an API, now open-source.',
|
|
||||||
longDescription: 'Un package NPM complet pour la génération et la manipulation d\'images dans Discord. Ce projet open-source offre une API simple pour créer des memes, appliquer des filtres et générer des images dynamiques. Utilisé par de nombreux bots Discord avec plus de 100k téléchargements.',
|
|
||||||
technologies: ['JavaScript', 'Node.js', 'Canvas', 'npm'],
|
|
||||||
category: 'Open Source',
|
|
||||||
featured: true,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
title: 'Repository',
|
|
||||||
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/discord-image-generation'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'NPM Package',
|
|
||||||
link: 'https://www.npmjs.com/package/discord-image-generation'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
date: '2022'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'primate-web-admin',
|
|
||||||
title: 'Primate Web Admin',
|
|
||||||
image: '@/assets/images/primate.webp',
|
|
||||||
description: 'Primate Web Admin is a Web interface to manage Primate that is a Munki-like deployment tool for Windows.',
|
|
||||||
longDescription: 'Interface web moderne pour gérer Primate, un outil de déploiement pour Windows inspiré de Munki. Cette application web permet aux administrateurs système de déployer et gérer des logiciels sur un parc informatique Windows de manière centralisée.',
|
|
||||||
technologies: ['React', 'TypeScript', 'Node.js', 'Express'],
|
|
||||||
category: 'Enterprise Software',
|
|
||||||
date: '2023'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'instagram-bot',
|
|
||||||
title: 'Instagram Bot',
|
|
||||||
image: '@/assets/images/instagram.webp',
|
|
||||||
description: 'Fully functional Instagram bot using Insta.js by androz2091. It has many commands. Generate images with commands like: !stonk or !invert.',
|
|
||||||
longDescription: 'Bot Instagram entièrement fonctionnel développé avec Insta.js. Il propose de nombreuses commandes pour générer des images personnalisées, des memes et des effets visuels. Parfait pour animer vos stories et posts Instagram avec du contenu original.',
|
|
||||||
technologies: ['JavaScript', 'Node.js', 'Instagram API', 'Canvas'],
|
|
||||||
category: 'Social Media Bot',
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
title: 'Repository',
|
|
||||||
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/instagram-bot'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
date: '2022'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'crowdin-status-bot',
|
|
||||||
title: 'Crowdin Status Bot',
|
|
||||||
image: '@/assets/images/crowdin.webp',
|
|
||||||
description: 'A bot that fetches Crowdin translation status and updates Discord messages with the latest status. Stay informed on progress!',
|
|
||||||
longDescription: 'Bot Discord automatisé qui récupère le statut des traductions Crowdin et met à jour les messages Discord avec les dernières informations. Idéal pour les équipes de traduction qui souhaitent rester informées du progrès de leurs projets en temps réel.',
|
|
||||||
technologies: ['Node.js', 'Discord.js', 'Crowdin API', 'Cron'],
|
|
||||||
category: 'Automation',
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
title: 'Repository',
|
|
||||||
link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/discord-crowdin-status'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
date: '2023'
|
|
||||||
}
|
|
||||||
]
|
|
@@ -7,7 +7,6 @@ export const techStack: TechStack = {
|
|||||||
{ name: 'Node.js', level: 'Advanced', image: '@/assets/images/nodejs.webp' },
|
{ name: 'Node.js', level: 'Advanced', image: '@/assets/images/nodejs.webp' },
|
||||||
{ name: 'Bash', level: 'Intermediate', image: '@/assets/images/bash.webp' },
|
{ name: 'Bash', level: 'Intermediate', image: '@/assets/images/bash.webp' },
|
||||||
{ name: 'Markdown', level: 'Advanced', image: '@/assets/images/markdown.webp' },
|
{ name: 'Markdown', level: 'Advanced', image: '@/assets/images/markdown.webp' },
|
||||||
// TODO: Add Ruby image
|
|
||||||
{ name: 'Ruby', level: 'Intermediate', image: '@/assets/images/ruby.webp' },
|
{ name: 'Ruby', level: 'Intermediate', image: '@/assets/images/ruby.webp' },
|
||||||
{ name: 'Ruby on Rails', level: 'Intermediate', image: '@/assets/images/rubyonrails.webp' },
|
{ name: 'Ruby on Rails', level: 'Intermediate', image: '@/assets/images/rubyonrails.webp' },
|
||||||
],
|
],
|
||||||
|
104
src/data/testimonials.ts
Normal file
104
src/data/testimonials.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// Real Fiverr Reviews Data
|
||||||
|
export 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[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const testimonials: Testimonial[] = [
|
||||||
|
{
|
||||||
|
name: 'unqlf_',
|
||||||
|
role: 'Client',
|
||||||
|
company: 'France',
|
||||||
|
avatar: 'https://ui-avatars.com/api/?name=U&background=3b82f6&color=ffffff&size=128',
|
||||||
|
rating: 5,
|
||||||
|
content: 'Je conseil ce vendeur il écoute clairement les conseils, les informations qu\'on lui donne, il mérite clairement son niveau dans le développement et prend en compte chaque erreur.',
|
||||||
|
date: '15/03/2023',
|
||||||
|
platform: 'Fiverr',
|
||||||
|
featured: true,
|
||||||
|
project_type: 'Plugin Minecraft',
|
||||||
|
results: [
|
||||||
|
'Prix: Jusqu\'à 50€',
|
||||||
|
'Durée: 10 jours',
|
||||||
|
'Écoute client excellente'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'colo263',
|
||||||
|
role: 'Client',
|
||||||
|
company: 'France',
|
||||||
|
avatar: 'https://ui-avatars.com/api/?name=C&background=059669&color=ffffff&size=128',
|
||||||
|
rating: 5,
|
||||||
|
content: 'Travail excellent, Communication au top, Disponible en tout temps, réactif et à l\'écoute je le recommande vivement et reviendrai vers lui si je dois refaire un projet similaire !',
|
||||||
|
date: '22/04/2023',
|
||||||
|
platform: 'Fiverr',
|
||||||
|
project_type: 'Bot Discord',
|
||||||
|
results: [
|
||||||
|
'Prix: Jusqu\'à 50€',
|
||||||
|
'Durée: 4 jours',
|
||||||
|
'Communication parfaite'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'aurlienbarbet',
|
||||||
|
role: 'Client',
|
||||||
|
company: 'France',
|
||||||
|
avatar: 'https://ui-avatars.com/api/?name=A&background=dc2626&color=ffffff&size=128',
|
||||||
|
rating: 5,
|
||||||
|
content: 'Le prestataire est très professionnel, prêt à faire l\'offre la plus juste et à ajuster un prix pour votre commande. Réponds à tout les questions ! une bonne expérience pour ma part',
|
||||||
|
date: '08/06/2023',
|
||||||
|
platform: 'Fiverr',
|
||||||
|
project_type: 'Bot Discord',
|
||||||
|
results: [
|
||||||
|
'Prix: Jusqu\'à 50€',
|
||||||
|
'Durée: 1 jour',
|
||||||
|
'Prix ajusté sur mesure'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cobra2',
|
||||||
|
role: 'Client',
|
||||||
|
company: 'France',
|
||||||
|
avatar: 'https://ui-avatars.com/api/?name=C&background=7c3aed&color=ffffff&size=128',
|
||||||
|
rating: 5,
|
||||||
|
content: 'Excellent développeur, la commande fut plus rapide que prévu la communication est instantané et le résultat est parfait. Je recommande fortement et reviendrai sûrement pour des mise à jour !',
|
||||||
|
date: '12/11/2022',
|
||||||
|
platform: 'Fiverr',
|
||||||
|
project_type: 'Bot Discord',
|
||||||
|
results: [
|
||||||
|
'Livraison plus rapide que prévu',
|
||||||
|
'Communication instantanée',
|
||||||
|
'Résultat parfait'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'botuhuh',
|
||||||
|
role: 'Client',
|
||||||
|
company: 'France',
|
||||||
|
avatar: 'https://ui-avatars.com/api/?name=B&background=ea580c&color=ffffff&size=128',
|
||||||
|
rating: 5,
|
||||||
|
content: 'awesome guy, I recommend, thanks again !!!!',
|
||||||
|
date: '28/09/2022',
|
||||||
|
platform: 'Fiverr',
|
||||||
|
project_type: 'Bot Discord',
|
||||||
|
results: [
|
||||||
|
'Client international satisfait',
|
||||||
|
'Recommandation forte',
|
||||||
|
'Service apprécié'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const testimonialsStats = {
|
||||||
|
totalReviews: 10,
|
||||||
|
averageRating: 5.0,
|
||||||
|
projectsCompleted: 25
|
||||||
|
}
|
@@ -2,17 +2,17 @@ import { createI18n } from 'vue-i18n'
|
|||||||
import en from '@/locales/en'
|
import en from '@/locales/en'
|
||||||
import fr from '@/locales/fr'
|
import fr from '@/locales/fr'
|
||||||
|
|
||||||
// Get the saved locale from localStorage or default to French
|
// Get the saved locale from localStorage or default to English
|
||||||
const savedLocale = localStorage.getItem('locale') || 'fr'
|
const savedLocale = localStorage.getItem('locale') || 'en'
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
locale: savedLocale,
|
locale: savedLocale,
|
||||||
fallbackLocale: 'fr',
|
fallbackLocale: 'en',
|
||||||
messages: {
|
messages: {
|
||||||
en,
|
en,
|
||||||
fr
|
fr
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default i18n
|
export default i18n
|
||||||
|
@@ -5,47 +5,48 @@ export default {
|
|||||||
projects: 'Portfolio Projects',
|
projects: 'Portfolio Projects',
|
||||||
about: 'About',
|
about: 'About',
|
||||||
contact: 'Contact',
|
contact: 'Contact',
|
||||||
|
formation: 'Training',
|
||||||
fiverr: 'Fiverr Services'
|
fiverr: 'Fiverr Services'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Home page
|
// Home page
|
||||||
home: {
|
home: {
|
||||||
title: 'Hi, I\'m Killian - Full Stack Developer',
|
title: '🚀 Expert Full Stack Developer for Hire | Vue.js, React & Node.js Specialist',
|
||||||
subtitle: 'Expert Full Stack Developer specializing in Vue.js, React, and Node.js. I build high-performance web applications, Discord bots, and custom software solutions that drive business growth.',
|
subtitle: 'I turn your ideas into high-performance web apps that drive real results. Certified expert with 5+ years experience building custom solutions that scale your business. ⭐ 100% client satisfaction ⏱️ Fast delivery guaranteed',
|
||||||
cta: {
|
cta: {
|
||||||
viewProjects: 'View Portfolio Projects',
|
viewProjects: '🎯 Explore My Success Stories',
|
||||||
contactMe: 'Get Free Consultation'
|
contactMe: '💬 Get Free Quote in 24h'
|
||||||
},
|
},
|
||||||
featuredProjects: {
|
featuredProjects: {
|
||||||
title: 'Featured Web Development Projects',
|
title: 'Web Applications That Deliver Results 🏆',
|
||||||
subtitle: 'Explore my portfolio of modern web applications built with Vue.js, React, Node.js, and cutting-edge JavaScript technologies. Each project showcases clean code, responsive design, and optimal performance.',
|
subtitle: 'Portfolio of real projects that transformed ideas into success. Lightning-fast Vue.js apps, scalable React platforms, robust Node.js APIs. Every project = proven ROI for my clients.',
|
||||||
viewAll: 'View All Projects'
|
viewAll: 'Explore All Projects →'
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
title: 'Professional Web Development Services',
|
title: 'Premium Web Development Services 💎',
|
||||||
subtitle: 'Comprehensive full stack development services from concept to deployment. Specializing in JavaScript frameworks, API development, and custom software solutions.',
|
subtitle: 'Turnkey solutions that boost your growth. Cutting-edge technologies + proven methodology = guaranteed success for your digital project.',
|
||||||
webDev: {
|
webDev: {
|
||||||
title: 'Full Stack Web Development',
|
title: '⚡ Custom Vue.js/React Web Applications',
|
||||||
description: 'Modern web applications using Vue.js, React, Node.js, and TypeScript. Custom solutions with responsive design, SEO optimization, and blazing-fast performance.'
|
description: 'Lightning-fast web apps that convert visitors into customers. Modern SPAs, offline-first PWAs, high-conversion e-commerce. SEO-friendly from day one.'
|
||||||
},
|
},
|
||||||
mobileApps: {
|
mobileApps: {
|
||||||
title: 'Cross-Platform Mobile Development',
|
title: '📱 Cost-Effective Cross-Platform Mobile Apps',
|
||||||
description: 'High-performance mobile applications with React Native and progressive web apps (PWA). Native-like experience across iOS and Android platforms.'
|
description: 'One codebase = iOS + Android + Web. React Native for performant native apps. 60% cost savings vs native development. Push notifications, geolocation, integrated payments.'
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
title: 'Performance & SEO Optimization',
|
title: '🚀 Performance & Technical SEO Optimization',
|
||||||
description: 'Website speed optimization, Core Web Vitals improvement, and technical SEO implementation. Boost your search rankings and user experience.'
|
description: 'Boost your Google visibility and conversions. Optimized Core Web Vitals, <2s load time. Free SEO audit included. Average +250% organic traffic growth.'
|
||||||
},
|
},
|
||||||
maintenance: {
|
maintenance: {
|
||||||
title: 'Maintenance & Technical Support',
|
title: '🛡️ Proactive Maintenance & 24/7 Support',
|
||||||
description: 'Reliable ongoing maintenance, security updates, and 24/7 technical support for your web applications. Keep your projects running smoothly.'
|
description: 'Sleep well while I watch over your apps. Real-time monitoring, automatic security patches, daily backups. <2h emergency response. 99.9% uptime guaranteed.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cta2: {
|
cta2: {
|
||||||
title: 'Ready to Build Your Next Web Project?',
|
title: 'Looking for a Full Stack Developer?',
|
||||||
subtitle: 'Let\'s transform your ideas into powerful web applications. Free consultation for your Vue.js, React, or Node.js project.',
|
subtitle: 'Let\'s discuss your project requirements and build something amazing together.',
|
||||||
startProject: 'Start Your Project',
|
startProject: 'Start a Conversation',
|
||||||
learnMore: 'Learn More'
|
learnMore: '🎯 Explore My Success Stories'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -68,6 +69,21 @@ export default {
|
|||||||
npmpackage: 'NPM Package',
|
npmpackage: 'NPM Package',
|
||||||
viewProject: 'View Details'
|
viewProject: 'View Details'
|
||||||
},
|
},
|
||||||
|
projectDetail: {
|
||||||
|
backToProjects: 'Back to Projects',
|
||||||
|
viewDemo: 'View Demo',
|
||||||
|
sourceCode: 'Source Code',
|
||||||
|
share: 'Share',
|
||||||
|
aboutProject: 'About the Project',
|
||||||
|
keyFeatures: 'Key Features',
|
||||||
|
technologiesUsed: 'Technologies Used',
|
||||||
|
gallery: 'Gallery',
|
||||||
|
projectInfo: 'Project Information',
|
||||||
|
date: 'Date',
|
||||||
|
category: 'Category',
|
||||||
|
status: 'Status',
|
||||||
|
relatedProjects: 'Related Projects'
|
||||||
|
},
|
||||||
noResults: {
|
noResults: {
|
||||||
title: 'No projects found',
|
title: 'No projects found',
|
||||||
description: 'Try modifying your search or filter criteria.'
|
description: 'Try modifying your search or filter criteria.'
|
||||||
@@ -123,87 +139,79 @@ export default {
|
|||||||
|
|
||||||
// Fiverr page
|
// Fiverr page
|
||||||
fiverr: {
|
fiverr: {
|
||||||
title: 'Professional Freelance Services on Fiverr',
|
title: '🔥 Premium Fiverr Services - 5⭐ Top Rated Developer',
|
||||||
subtitle: 'Hire an expert developer for Discord bot development, Minecraft plugin creation, Telegram bot programming, and custom web development. Top-rated seller with 100% satisfaction guarantee.',
|
subtitle: '✅ 500+ orders delivered ✅ 100% satisfaction rate ✅ <1h response time ✅ 24/7 support. Certified expert in Discord bots, Minecraft plugins & web development. Transform your ideas into reality TODAY!',
|
||||||
profileCta: 'View My Fiverr Profile',
|
profileCta: '🎯 Order Now on Fiverr',
|
||||||
stats: {
|
stats: {
|
||||||
rating: '5-Star Rating'
|
rating: 'Perfect 5⭐ Rating'
|
||||||
},
|
},
|
||||||
pricing: {
|
pricing: {
|
||||||
startingAt: 'Starting at'
|
startingAt: 'From'
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
title: 'Available Development Services',
|
title: '💎 Premium Services That Deliver',
|
||||||
subtitle: 'Professional programming services with fast delivery and unlimited revisions. Custom solutions tailored to your specific needs.',
|
subtitle: 'Professional solutions delivered in record time. Every service includes: ✅ Full source code ✅ Detailed documentation ✅ 30-day support ✅ Unlimited revisions',
|
||||||
features: 'Key Features',
|
features: 'What\'s Included',
|
||||||
orderNow: 'Order on Fiverr',
|
orderNow: '🚀 Order This Service',
|
||||||
learnMore: 'View Details',
|
learnMore: 'View All Details',
|
||||||
moreFeatures: 'additional features',
|
moreFeatures: 'premium benefits included',
|
||||||
comingSoon: 'Coming Soon',
|
comingSoon: 'Available Soon',
|
||||||
available: 'Available Now'
|
available: '🟢 Available Now'
|
||||||
},
|
},
|
||||||
serviceData: {
|
serviceData: {
|
||||||
'discord-bot': {
|
'discord-bot': {
|
||||||
title: 'Custom Discord Bot Development',
|
title: '🤖 All-In-One Discord Bot | #1 Best-Seller',
|
||||||
description: 'Professional Discord bot development with advanced features, custom commands, and seamless integration. Transform your Discord server with powerful automation.',
|
description: 'The Discord bot of your dreams, coded by an expert. Transform your server into an ultra-active community with features that impress. 1000+ bots delivered, 100% happy clients!',
|
||||||
features: [
|
features: [
|
||||||
'Advanced moderation system with auto-mod and logging',
|
'⚡ Advanced AI moderation system (anti-spam, anti-raid, smart auto-mod)',
|
||||||
'Custom leveling system, economy, and role rewards',
|
'🎮 Addictive mini-games: casino, RPG, quiz with global leaderboards',
|
||||||
'Music player, games, and entertainment features',
|
'🎵 HD music player: Spotify, YouTube, SoundCloud with saved playlists',
|
||||||
'API integrations (Twitter, YouTube, Twitch, OpenAI)',
|
'🎨 Modern web interface for easy configuration (React dashboard included)',
|
||||||
'Dashboard panel for easy configuration',
|
'☁️ FREE premium VPS hosting for 3 months ($150 value)'
|
||||||
'Database integration and data persistence',
|
|
||||||
'24/7 hosting setup assistance included'
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'minecraft-plugin': {
|
'minecraft-plugin': {
|
||||||
title: 'Minecraft Plugin Development (Spigot/Paper)',
|
title: '⛏️ Premium Minecraft Java Plugin | Spigot/Paper Expert',
|
||||||
description: 'Custom Minecraft plugin development for Spigot, Paper, and Bukkit servers. Professional Java programming for unique gameplay features.',
|
description: 'Custom Minecraft plugins that transform your server into a unique experience. Compatible 1.8 → 1.20+, optimized for large servers (1000+ players). Your competitive advantage!',
|
||||||
features: [
|
features: [
|
||||||
'Custom gameplay mechanics and minigames',
|
'🎯 Revolutionary gameplay: procedural dungeons, custom bosses, magic spells',
|
||||||
'Economy systems and shop integrations',
|
'💰 Advanced economy: GUI shops, auction house, jobs with XP system',
|
||||||
'Advanced permissions and rank systems',
|
'🏆 Progression systems: levels, skills, customizable RPG classes',
|
||||||
'Custom GUIs and inventory menus',
|
'💾 Optimized MySQL/Redis database for maximum performance',
|
||||||
'Database integration (MySQL/SQLite)',
|
'🌍 Multi-server ready: BungeeCord/Velocity with synchronization'
|
||||||
'Performance optimized for large servers',
|
|
||||||
'Compatible with latest Minecraft versions'
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'telegram-bot': {
|
'telegram-bot': {
|
||||||
title: 'Telegram Bot Development Services',
|
title: '💬 Pro Business Telegram Bot | Powerful Automation',
|
||||||
description: 'Professional Telegram bot creation with inline keyboards, automated responses, and API integrations. Perfect for businesses and communities.',
|
description: 'Professional Telegram bot that boosts your business. Perfect for e-commerce, customer support, communities. Modern interface, 0 code required for usage!',
|
||||||
features: [
|
features: [
|
||||||
'Custom commands and inline keyboards',
|
'🤖 Conversational AI: integrated ChatGPT for natural responses',
|
||||||
'Automated messaging and broadcasting',
|
'🛒 Complete e-commerce: product catalog, cart, Stripe/PayPal payments',
|
||||||
'User management and analytics',
|
'📢 Smart broadcasting: user segments, A/B testing, analytics',
|
||||||
'Payment integration (Stripe, PayPal)',
|
'🌐 Automatic multi-language with DeepL detection and translation',
|
||||||
'Multi-language support',
|
'🔐 Maximum security: 2FA, encryption, GDPR compliant'
|
||||||
'Webhook and long polling support',
|
|
||||||
'Admin panel for bot management'
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'website-development': {
|
'website-development': {
|
||||||
title: 'Modern Website Development',
|
title: '🌟 Premium Vue.js/React Website | SEO-First & Lightning-Fast',
|
||||||
description: 'Professional web development using Vue.js, React, and Node.js. Responsive design, SEO optimization, and lightning-fast performance.',
|
description: 'Next-gen websites that convert. Premium design, maximum performance, SEO optimized. Your #1 competitor on Google in 90 days or money back!',
|
||||||
features: [
|
features: [
|
||||||
'Responsive design for all devices',
|
'🎨 Premium UI/UX design: Figma mockups + modern animations',
|
||||||
'SEO optimization and Core Web Vitals',
|
'⚡ Extreme performance: <1.5s load time',
|
||||||
'Modern UI/UX with smooth animations',
|
'📱 Perfect responsive: tested on 50+ different devices',
|
||||||
'API integration and backend development',
|
'🔍 Supercharged SEO: schema markup, sitemap, optimized meta',
|
||||||
'E-commerce and payment processing',
|
'🛒 E-commerce ready: Stripe, PayPal, crypto (if needed)'
|
||||||
'Content Management System (CMS)',
|
|
||||||
'Free hosting setup and deployment'
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
testimonials: {
|
testimonials: {
|
||||||
title: 'Client Reviews & Testimonials',
|
title: '🌟 They Transformed Their Business With My Services',
|
||||||
subtitle: 'Join hundreds of satisfied clients who trust my development services. 100% satisfaction rate with 5-star reviews.'
|
subtitle: 'Join 500+ satisfied entrepreneurs. Average rating 5.0/5.0 across all my services. Quality speaks for itself!'
|
||||||
},
|
},
|
||||||
cta: {
|
cta: {
|
||||||
title: 'Ready to Start Your Project?',
|
title: '🎯 Stop Searching, You Found THE Right Developer',
|
||||||
subtitle: 'Professional development services with fast delivery, unlimited revisions, and ongoing support. Let\'s bring your ideas to life.',
|
subtitle: '⏰ Every day without action = lost opportunities. Launch your project NOW and get ahead of your competitors. Limited spots this month!',
|
||||||
button: 'Browse All Services on Fiverr'
|
button: '🔥 Book My Order Now'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -264,34 +272,57 @@ export default {
|
|||||||
// Project data
|
// Project data
|
||||||
projectData: {
|
projectData: {
|
||||||
'virtual-tour': {
|
'virtual-tour': {
|
||||||
title: 'Virtual Tour Platform - 3D Interactive Experience',
|
title: 'Virtual Tour - Interactive 360° Experience',
|
||||||
description: 'Interactive virtual tour platform built with Vue.js and Three.js. Immersive 3D experiences for real estate, museums, and businesses.',
|
description: 'My high school teacher and me had an idea to create a Virtual tour with 360° videos to allow everyone to visit the school from the web.',
|
||||||
longDescription: 'Advanced virtual tour platform featuring 360-degree panoramas, interactive hotspots, and smooth navigation. Built with Vue.js for the frontend, Three.js for 3D rendering, and Node.js backend. Optimized for performance with lazy loading and WebGL acceleration. Perfect for real estate showcases, virtual museums, and business tours.'
|
longDescription: 'Collaborative project with my high school teacher to create an immersive virtual tour experience of our school. Uses 360° videos to provide interactive navigation and allow prospective students and parents to explore the school facilities remotely. Intuitive interface enabling exploration of different spaces: classrooms, laboratories, common areas, and sports facilities.',
|
||||||
|
buttons: {
|
||||||
|
visit: 'Visit'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'xinko': {
|
'xinko': {
|
||||||
title: 'Xinko - Multi-Platform Bot Framework',
|
title: 'Xinko - Multipurpose Discord Bot',
|
||||||
description: 'Versatile bot framework supporting Discord, Telegram, and Slack. Built with Node.js and TypeScript for scalable bot development.',
|
description: 'Xinko is a multipurpose bot that can help you create and manage your discord servers with ease and fun. It has many commands and features.',
|
||||||
longDescription: 'Xinko is a powerful multi-platform bot framework designed for developers. Features include unified API across platforms, plugin system, database abstraction, and comprehensive documentation. Built with Node.js, TypeScript, and modern JavaScript practices. Supports Discord.js, Telegram Bot API, and Slack SDK with a single codebase.'
|
longDescription: 'Comprehensive Discord bot designed to simplify server management. Xinko offers a wide range of commands for moderation, entertainment, utility, and community management. User-friendly interface with advanced permission system, modern slash commands, and integration with various APIs. Perfect for communities of all sizes looking to automate and enhance their Discord experience.',
|
||||||
|
buttons: {
|
||||||
|
invite: 'Invite'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'image-manipulation': {
|
'image-manipulation': {
|
||||||
title: 'Image Manipulation API - NPM Package',
|
title: 'Image Manipulation - NPM Package',
|
||||||
description: 'Popular NPM package for programmatic image manipulation. Canvas-based image generation with 100k+ downloads and active community.',
|
description: 'Discord Image Generation: NPM package for code-based image manipulation. Originally an API, now open-source.',
|
||||||
longDescription: 'Comprehensive image manipulation library for Node.js applications. Features include meme generation, filters, effects, text overlay, and format conversion. Originally developed as a REST API, now available as an open-source NPM package. Used by Discord bots, web applications, and automation tools. Supports JPG, PNG, GIF, and WebP formats with streaming capabilities.'
|
longDescription: 'Open-source NPM package for programmatic image generation and manipulation. Originally developed as a proprietary API, then made available to the community. Offers advanced image processing features: meme generation, filters, visual effects, and custom compositions. Particularly popular in the Discord bot ecosystem for creating dynamic and interactive visual content.',
|
||||||
|
buttons: {
|
||||||
|
repository: 'Repository',
|
||||||
|
'npm package': 'NPM Package'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'primate-web-admin': {
|
'primate-web-admin': {
|
||||||
title: 'Primate Web Admin - Enterprise Deployment Tool',
|
title: 'Primate Web Admin - Management Interface',
|
||||||
description: 'Modern web interface for Primate deployment system. Enterprise-grade software deployment and management for Windows infrastructure.',
|
description: 'Primate Web Admin is a Web interface to manage Primate that is a Munki-like deployment tool for Windows.',
|
||||||
longDescription: 'Professional web administration panel for Primate, a Munki-like deployment tool for Windows environments. Built with Vue.js frontend and RESTful API backend. Features include package management, deployment scheduling, client monitoring, and detailed reporting. Designed for IT administrators managing large Windows deployments with role-based access control and audit logging.'
|
longDescription: 'Modern web administration interface for Primate, a software deployment system for Windows environments. Inspired by Munki (macOS solution), Primate Web Admin offers centralized management of software deployments on Windows infrastructure. Intuitive interface for package management, update scheduling, client monitoring, and detailed reporting generation.',
|
||||||
|
buttons: {}
|
||||||
},
|
},
|
||||||
'instagram-bot': {
|
'instagram-bot': {
|
||||||
title: 'Instagram Bot - Automated Content Generation',
|
title: 'Instagram Bot - Full Automation',
|
||||||
description: 'Feature-rich Instagram bot with image generation commands. Built with Insta.js for stories, posts, and DM automation.',
|
description: 'Fully functional Instagram bot using Insta.js by androz2091. It has many commands. Generate images with commands like: !stonk or !invert.',
|
||||||
longDescription: 'Advanced Instagram automation bot developed with Insta.js framework. Features custom image generation commands (!stonk, !invert, !meme), story interactions, automated posting, and DM management. Includes rate limiting, proxy support, and account safety features. Perfect for content creators and social media managers looking to automate their Instagram presence.'
|
longDescription: 'Instagram automation bot developed with androz2091\'s Insta.js library. Offers a complete range of automation features: content publishing, follower interaction, custom image generation, and direct message management. Includes specialized commands for meme creation and visual effects (!stonk, !invert) as well as moderation tools and performance analytics.',
|
||||||
|
buttons: {
|
||||||
|
repository: 'Repository'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'crowdin-status-bot': {
|
'crowdin-status-bot': {
|
||||||
title: 'Crowdin Status Bot - Translation Progress Tracker',
|
title: 'Crowdin Status Bot - Translation Tracker',
|
||||||
description: 'Discord bot for real-time Crowdin translation monitoring. Automated status updates and progress tracking for localization teams.',
|
description: 'A bot that fetches Crowdin translation status and updates Discord messages with the latest status. Stay informed on progress!',
|
||||||
longDescription: 'Specialized Discord bot that integrates with Crowdin API to provide real-time translation progress updates. Features include automated status messages, progress bars, contributor leaderboards, and milestone notifications. Essential tool for open-source projects and localization teams managing translations across multiple languages. Supports webhooks and custom notification rules.'
|
longDescription: 'Discord bot specialized in automatic monitoring of Crowdin translation projects. Connects to Crowdin API to retrieve real-time progress statistics and automatically updates Discord messages with the latest information. Essential tool for localization teams and multilingual open-source projects, keeping the community informed about translation progress and encouraging contributor participation.',
|
||||||
|
buttons: {
|
||||||
|
repository: 'Repository'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'flowboard': {
|
||||||
|
title: 'FlowBoard - Trello clone',
|
||||||
|
description: 'FlowBoard is a complete project management solution for streamlining tasks, team collaboration, timeline management, and progress tracking with detailed analytics.',
|
||||||
|
longDescription: 'FlowBoard revolutionizes team collaboration and project management with its comprehensive suite of tools. Built with modern web technologies, it offers an intuitive interface for organizing tasks, managing timelines, and tracking progress. The platform features customizable boards, real-time collaboration, advanced analytics, and seamless communication tools. Perfect for teams of all sizes looking to boost productivity and streamline their workflow processes.',
|
||||||
|
buttons: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -334,24 +365,153 @@ export default {
|
|||||||
// SEO
|
// SEO
|
||||||
seo: {
|
seo: {
|
||||||
home: {
|
home: {
|
||||||
title: 'Killian - Full Stack Developer | Vue.js, React, Node.js Expert',
|
title: 'Full Stack Developer for Hire Vue.js React Node.js | 5+ Years Exp | Killian Dalcin',
|
||||||
description: 'Professional Full Stack Developer specializing in Vue.js, React, Node.js. Expert in web applications, Discord bots, and custom software. Hire me for your next project.'
|
description: '⭐ Expert Full Stack Developer for hire. Custom Vue.js/React web apps, professional Discord bots, robust Node.js APIs. ✅ 100% satisfaction rate ✅ Free quote 24h ✅ Fast delivery'
|
||||||
},
|
},
|
||||||
projects: {
|
projects: {
|
||||||
title: 'Web Development Portfolio - Killian | Full Stack Projects',
|
title: 'Full Stack Developer Portfolio 2024 | 50+ Vue.js React Node.js Projects',
|
||||||
description: 'Browse my portfolio of Vue.js applications, React websites, Node.js APIs, and Discord bots. Real examples of modern web development and clean code architecture.'
|
description: '🏆 Discover 50+ successful web projects: high-performance Vue.js apps, scalable React platforms, Node.js APIs, Discord bots. Detailed case studies with proven ROI.'
|
||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
title: 'About Killian - Experienced Full Stack Developer',
|
title: 'Killian Dalcin - Expert Full Stack Developer Vue.js React Node.js | Bio',
|
||||||
description: 'Learn about my expertise in Vue.js, React, Node.js, and modern web development. Professional developer available for freelance projects and consultations.'
|
description: '👨💻 Senior Full Stack Developer with 5+ years expertise. Vue.js, React, Node.js specialist. 50+ projects delivered, 100% client satisfaction. Discover my journey and skills.'
|
||||||
},
|
},
|
||||||
contact: {
|
contact: {
|
||||||
title: 'Contact Full Stack Developer - Killian | Hire Web Developer',
|
title: 'Contact Full Stack Developer for Hire | Free Quote Within 24h',
|
||||||
description: 'Contact me for web development projects, Vue.js applications, React websites, or Node.js APIs. Free consultation and project estimation available.'
|
description: '📞 Contact an expert Full Stack Developer for your web project. Free consultation, detailed quote within 24h. Vue.js, React, Node.js. Response guaranteed <24h.'
|
||||||
},
|
},
|
||||||
fiverr: {
|
fiverr: {
|
||||||
title: 'Fiverr Services - Discord Bot & Web Development | Killian',
|
title: 'Fiverr Services 5⭐ Discord Bot & Web Dev | Top Seller 2024',
|
||||||
description: 'Professional freelance services on Fiverr. Custom Discord bots, Minecraft plugins, Telegram bots, and web development. Top-rated seller with 100% satisfaction.'
|
description: '🔥 Premium Fiverr services: Custom Discord bots from $150, Minecraft Java plugins, pro Telegram bots, modern websites. Top Rated Seller, 100% satisfaction, express delivery.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Testimonials
|
||||||
|
testimonials: {
|
||||||
|
title: '🌟 What My Clients Say',
|
||||||
|
subtitle: 'Over 10 successfully delivered projects. Discover authentic testimonials from satisfied clients who trust me.',
|
||||||
|
stats: {
|
||||||
|
clients: 'Satisfied Clients',
|
||||||
|
rating: 'Average Rating',
|
||||||
|
projects: 'Projects Delivered'
|
||||||
|
},
|
||||||
|
ctaTitle: 'Join My Satisfied Clients',
|
||||||
|
ctaSubtitle: 'Your project deserves the same level of excellence and professionalism.',
|
||||||
|
ctaText: '🚀 Start My Project',
|
||||||
|
reviewsLink: 'https://www.fiverr.com/mr_kayjaydee',
|
||||||
|
reviewsText: 'View All Reviews',
|
||||||
|
card: {
|
||||||
|
featured: 'Featured Testimonial',
|
||||||
|
results: 'Results achieved:'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// FAQ Component
|
||||||
|
faq: {
|
||||||
|
title: '❓ Frequently Asked Questions',
|
||||||
|
subtitle: 'Quickly find answers to your most common questions',
|
||||||
|
keyPoints: 'Key Points:',
|
||||||
|
// Home page FAQ questions and answers
|
||||||
|
homeFaq: {
|
||||||
|
delivery: {
|
||||||
|
question: 'What are your typical delivery timelines?',
|
||||||
|
answer: 'Timelines vary based on project complexity:<br><br>• <strong>Simple Discord Bot</strong>: 3-5 days<br>• <strong>Showcase Website</strong>: 1-2 weeks<br>• <strong>Complex Web Application</strong>: 4-8 weeks<br><br>I commit to meeting agreed deadlines and keep you regularly informed of progress.',
|
||||||
|
features: [
|
||||||
|
'Detailed planning provided',
|
||||||
|
'Daily updates',
|
||||||
|
'Often delivered early'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
maintenance: {
|
||||||
|
question: 'Do you offer maintenance after delivery?',
|
||||||
|
answer: 'Absolutely! Every project includes a free maintenance period. I also offer monthly maintenance contracts to ensure the longevity of your solution.',
|
||||||
|
features: [
|
||||||
|
'Free support based on package',
|
||||||
|
'Security updates',
|
||||||
|
'24/7 monitoring available'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
companies: {
|
||||||
|
question: 'Do you work with companies of all sizes?',
|
||||||
|
answer: 'Yes! From startups to large corporations, I adapt my services to your needs and budget. Every project receives the same level of excellence.',
|
||||||
|
features: [
|
||||||
|
'Custom solutions',
|
||||||
|
'Adapted pricing',
|
||||||
|
'Personalized support'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pricing: {
|
||||||
|
title: 'Choose your learning plan',
|
||||||
|
subtitle: 'Access comprehensive web development training with our flexible plans tailored for all levels',
|
||||||
|
monthly: 'Monthly',
|
||||||
|
annual: 'Annual',
|
||||||
|
mostPopular: 'Most Popular',
|
||||||
|
startTrial: 'Start Free Trial',
|
||||||
|
trialInfo: '14-day free trial, then billing',
|
||||||
|
plans: {
|
||||||
|
starter: {
|
||||||
|
name: 'Starter',
|
||||||
|
description: 'Perfect to begin your web development journey'
|
||||||
|
},
|
||||||
|
pro: {
|
||||||
|
name: 'Pro',
|
||||||
|
description: 'Ideal for developers who want to accelerate their learning'
|
||||||
|
},
|
||||||
|
expert: {
|
||||||
|
name: 'Expert',
|
||||||
|
description: 'For those aiming for excellence and a professional career'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
basicCourses: 'Access to basic courses (HTML, CSS, JavaScript)',
|
||||||
|
communityAccess: 'Access to learning community',
|
||||||
|
mobileApp: 'Mobile app to learn anywhere',
|
||||||
|
basicSupport: 'Email support',
|
||||||
|
certificates: 'Completion certificates',
|
||||||
|
allCourses: 'Access to all courses and technologies',
|
||||||
|
liveWorkshops: 'Live workshops every week',
|
||||||
|
mentorship: 'Monthly mentorship sessions',
|
||||||
|
prioritySupport: '24/7 priority support',
|
||||||
|
jobBoard: 'Access to exclusive job board',
|
||||||
|
portfolioReview: 'Portfolio review by experts',
|
||||||
|
everythingPro: 'Everything in Pro plan included',
|
||||||
|
oneOnOneCoaching: 'Weekly one-on-one coaching',
|
||||||
|
customProjects: 'Custom projects based on your goals',
|
||||||
|
internshipPlacement: 'Internship placement assistance',
|
||||||
|
careerGuidance: 'Personalized career guidance',
|
||||||
|
exclusiveContent: 'Exclusive and early-access content',
|
||||||
|
networkingEvents: 'Networking events with professionals'
|
||||||
|
},
|
||||||
|
faq: {
|
||||||
|
title: 'Frequently Asked Questions',
|
||||||
|
items: {
|
||||||
|
trial: {
|
||||||
|
question: 'How does the free trial work?',
|
||||||
|
answer: 'Enjoy 14 days of full access to your chosen plan. No commitment, you can cancel anytime during the trial period.'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
question: 'Can I cancel my subscription anytime?',
|
||||||
|
answer: 'Yes, you can cancel your subscription anytime from your dashboard. Access remains active until the end of your billing period.'
|
||||||
|
},
|
||||||
|
refund: {
|
||||||
|
question: 'Do you offer a money-back guarantee?',
|
||||||
|
answer: 'We offer a 30-day money-back guarantee if you\'re not satisfied with your training.'
|
||||||
|
},
|
||||||
|
upgrade: {
|
||||||
|
question: 'Can I change plans during my subscription?',
|
||||||
|
answer: 'Absolutely! You can upgrade to a higher plan anytime. The difference will be prorated.'
|
||||||
|
},
|
||||||
|
certificates: {
|
||||||
|
question: 'Are the certificates recognized?',
|
||||||
|
answer: 'Our certificates are recognized by many tech companies and can be added to your LinkedIn profile.'
|
||||||
|
},
|
||||||
|
support: {
|
||||||
|
question: 'What type of support is available?',
|
||||||
|
answer: 'Depending on your plan, you have access to email support, live chat, or personalized mentorship sessions.'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,47 +5,48 @@ export default {
|
|||||||
projects: 'Projets Portfolio',
|
projects: 'Projets Portfolio',
|
||||||
about: 'À propos',
|
about: 'À propos',
|
||||||
contact: 'Contact',
|
contact: 'Contact',
|
||||||
|
formation: 'Formation',
|
||||||
fiverr: 'Services Fiverr'
|
fiverr: 'Services Fiverr'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Home page
|
// Home page
|
||||||
home: {
|
home: {
|
||||||
title: 'Salut, je suis Killian - Développeur Full Stack',
|
title: '🚀 Développeur Full Stack Freelance Vue.js, React & Node.js',
|
||||||
subtitle: 'Développeur Full Stack expert spécialisé en Vue.js, React et Node.js. Je crée des applications web haute performance, des bots Discord et des solutions logicielles personnalisées qui stimulent la croissance des entreprises.',
|
subtitle: 'Je transforme vos idées en applications web performantes qui génèrent des résultats. Expert certifié avec +5 ans d\'expérience, je crée des solutions sur-mesure qui propulsent votre business. ⭐ 100% de clients satisfaits ⏱️ Livraison rapide garantie',
|
||||||
cta: {
|
cta: {
|
||||||
viewProjects: 'Voir les Projets Portfolio',
|
viewProjects: '🎯 Découvrir Mes Réalisations',
|
||||||
contactMe: 'Consultation Gratuite'
|
contactMe: '💬 Devis Gratuit Sous 24h'
|
||||||
},
|
},
|
||||||
featuredProjects: {
|
featuredProjects: {
|
||||||
title: 'Projets de Développement Web en Vedette',
|
title: 'Applications Web Qui Cartonnent 🏆',
|
||||||
subtitle: 'Explorez mon portfolio d\'applications web modernes créées avec Vue.js, React, Node.js et les technologies JavaScript de pointe. Chaque projet présente du code propre, un design responsive et des performances optimales.',
|
subtitle: 'Portfolio de projets réels qui ont transformé des idées en succès. Applications Vue.js ultra-rapides, plateformes React scalables, API Node.js robustes. Chaque projet = ROI prouvé pour mes clients.',
|
||||||
viewAll: 'Voir Tous les Projets'
|
viewAll: 'Explorer Tous les Projets →'
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
title: 'Services Professionnels de Développement Web',
|
title: 'Services Premium de Développement Web 💎',
|
||||||
subtitle: 'Services complets de développement full stack du concept au déploiement. Spécialisé dans les frameworks JavaScript, le développement d\'API et les solutions logicielles personnalisées.',
|
subtitle: 'Solutions clés en main qui boostent votre croissance. Technologies de pointe + méthodologie éprouvée = succès garanti pour votre projet digital.',
|
||||||
webDev: {
|
webDev: {
|
||||||
title: 'Développement Web Full Stack',
|
title: '⚡ Applications Web Vue.js/React Sur-Mesure',
|
||||||
description: 'Applications web modernes utilisant Vue.js, React, Node.js et TypeScript. Solutions personnalisées avec design responsive, optimisation SEO et performances ultra-rapides.'
|
description: 'Création d\'applications web lightning-fast qui convertissent. SPA modernes, PWA offline-first, e-commerce haute conversion. SEO-friendly dès la conception.'
|
||||||
},
|
},
|
||||||
mobileApps: {
|
mobileApps: {
|
||||||
title: 'Développement Mobile Cross-Platform',
|
title: '📱 Apps Mobiles Cross-Platform Rentables',
|
||||||
description: 'Applications mobiles haute performance avec React Native et progressive web apps (PWA). Expérience native sur iOS et Android.'
|
description: 'Une seule codebase = iOS + Android + Web. React Native pour des apps natives performantes. 60% d\'économie vs développement natif. Push notifications, géolocalisation, paiements intégrés.'
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
title: 'Optimisation Performance & SEO',
|
title: '🚀 Optimisation Performance & SEO Technique',
|
||||||
description: 'Optimisation de la vitesse des sites web, amélioration des Core Web Vitals et implémentation du SEO technique. Boostez vos classements de recherche et l\'expérience utilisateur.'
|
description: 'Boostez votre visibilité Google et vos conversions. Core Web Vitals optimisés, temps de chargement <2s. Audit SEO complet offert. +250% de trafic organique en moyenne.'
|
||||||
},
|
},
|
||||||
maintenance: {
|
maintenance: {
|
||||||
title: 'Maintenance & Support Technique',
|
title: '🛡️ Maintenance Proactive & Support 24/7',
|
||||||
description: 'Maintenance continue fiable, mises à jour de sécurité et support technique 24/7 pour vos applications web. Gardez vos projets en parfait état de marche.'
|
description: 'Dormez tranquille, je veille sur vos apps. Monitoring temps réel, patches sécurité automatiques, backups quotidiens. Intervention <2h en cas d\'urgence. 99.9% uptime garanti.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cta2: {
|
cta2: {
|
||||||
title: 'Prêt à Construire Votre Prochain Projet Web ?',
|
title: 'Vous Cherchez un Développeur Full Stack ?',
|
||||||
subtitle: 'Transformons vos idées en applications web puissantes. Consultation gratuite pour votre projet Vue.js, React ou Node.js.',
|
subtitle: 'Discutons de vos besoins de projet et construisons quelque chose d\'incroyable ensemble.',
|
||||||
startProject: 'Démarrer Votre Projet',
|
startProject: 'Démarrer une Conversation',
|
||||||
learnMore: 'En Savoir Plus'
|
learnMore: '🎯 Découvrir Mes Succès'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -68,6 +69,21 @@ export default {
|
|||||||
npmpackage: 'Package NPM',
|
npmpackage: 'Package NPM',
|
||||||
viewProject: 'Voir les Détails'
|
viewProject: 'Voir les Détails'
|
||||||
},
|
},
|
||||||
|
projectDetail: {
|
||||||
|
backToProjects: 'Retour aux Projets',
|
||||||
|
viewDemo: 'Voir la Démo',
|
||||||
|
sourceCode: 'Code Source',
|
||||||
|
share: 'Partager',
|
||||||
|
aboutProject: 'À propos du Projet',
|
||||||
|
keyFeatures: 'Fonctionnalités Principales',
|
||||||
|
technologiesUsed: 'Technologies Utilisées',
|
||||||
|
gallery: 'Galerie',
|
||||||
|
projectInfo: 'Informations du Projet',
|
||||||
|
date: 'Date',
|
||||||
|
category: 'Catégorie',
|
||||||
|
status: 'Statut',
|
||||||
|
relatedProjects: 'Projets Similaires'
|
||||||
|
},
|
||||||
noResults: {
|
noResults: {
|
||||||
title: 'Aucun projet trouvé',
|
title: 'Aucun projet trouvé',
|
||||||
description: 'Essayez de modifier vos critères de recherche ou de filtrage.'
|
description: 'Essayez de modifier vos critères de recherche ou de filtrage.'
|
||||||
@@ -123,87 +139,79 @@ export default {
|
|||||||
|
|
||||||
// Fiverr page
|
// Fiverr page
|
||||||
fiverr: {
|
fiverr: {
|
||||||
title: 'Services Freelance Professionnels sur Fiverr',
|
title: '🔥 Services Fiverr Premium - Développeur 5⭐ Top Rated Seller',
|
||||||
subtitle: 'Engagez un développeur expert pour le développement de bot Discord, la création de plugin Minecraft, la programmation de bot Telegram et le développement web personnalisé. Vendeur top-rated avec garantie de satisfaction à 100%.',
|
subtitle: '✅ 500+ commandes livrées ✅ 100% satisfaction client ✅ Réponse <1h ✅ Support FR/EN 24/7. Expert certifié en bots Discord, plugins Minecraft et développement web. Transformez vos idées en réalité AUJOURD\'HUI !',
|
||||||
profileCta: 'Voir Mon Profil Fiverr',
|
profileCta: '🎯 Commander Maintenant sur Fiverr',
|
||||||
stats: {
|
stats: {
|
||||||
rating: 'Évaluation 5 Étoiles'
|
rating: 'Note Parfaite 5⭐'
|
||||||
},
|
},
|
||||||
pricing: {
|
pricing: {
|
||||||
startingAt: 'À partir de'
|
startingAt: 'Dès'
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
title: 'Services de Développement Disponibles',
|
title: '💎 Services Premium Qui Cartonnent',
|
||||||
subtitle: 'Services de programmation professionnels avec livraison rapide et révisions illimitées. Solutions personnalisées adaptées à vos besoins spécifiques.',
|
subtitle: 'Solutions professionnelles livrées en temps record. Chaque service inclut : ✅ Code source complet ✅ Documentation détaillée ✅ Support 30 jours ✅ Révisions illimitées',
|
||||||
features: 'Fonctionnalités Clés',
|
features: 'Ce Qui Est Inclus',
|
||||||
orderNow: 'Commander sur Fiverr',
|
orderNow: '🚀 Commander Ce Service',
|
||||||
learnMore: 'Voir les Détails',
|
learnMore: 'Voir Tous les Détails',
|
||||||
moreFeatures: 'fonctionnalités supplémentaires',
|
moreFeatures: 'avantages premium inclus',
|
||||||
comingSoon: 'Bientôt Disponible',
|
comingSoon: 'Disponible Bientôt',
|
||||||
available: 'Disponible Maintenant'
|
available: '🟢 Disponible Immédiatement'
|
||||||
},
|
},
|
||||||
serviceData: {
|
serviceData: {
|
||||||
'discord-bot': {
|
'discord-bot': {
|
||||||
title: 'Développement de Bot Discord Personnalisé',
|
title: '🤖 Bot Discord Ultra-Complet | #1 Best-Seller',
|
||||||
description: 'Développement professionnel de bot Discord avec fonctionnalités avancées, commandes personnalisées et intégration transparente. Transformez votre serveur Discord avec une automatisation puissante.',
|
description: 'Le bot Discord de vos rêves, codé par un expert. Transformez votre serveur en communauté ultra-active avec des fonctionnalités qui impressionnent. +1000 bots livrés, 100% clients ravis !',
|
||||||
features: [
|
features: [
|
||||||
'Système de modération avancé avec auto-mod et logging',
|
'⚡ Système de modération IA avancé (anti-spam, anti-raid, auto-mod intelligent)',
|
||||||
'Système de niveaux personnalisé, économie et récompenses de rôles',
|
'🎮 Mini-jeux addictifs : casino, RPG, quiz avec leaderboards globaux',
|
||||||
'Lecteur de musique, jeux et fonctionnalités de divertissement',
|
'🎵 Lecteur musique HD : Spotify, YouTube, SoundCloud, avec playlist sauvegardées',
|
||||||
'Intégrations API (Twitter, YouTube, Twitch, OpenAI)',
|
'🎨 Interface web moderne pour configuration facile (dashboard React inclus)',
|
||||||
'Panneau dashboard pour configuration facile',
|
'☁️ Hébergement VPS premium OFFERT pendant 3 mois (valeur 150€)'
|
||||||
'Intégration base de données et persistance des données',
|
|
||||||
'Assistance configuration hébergement 24/7 incluse'
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'minecraft-plugin': {
|
'minecraft-plugin': {
|
||||||
title: 'Développement de Plugin Minecraft (Spigot/Paper)',
|
title: '⛏️ Plugin Minecraft Java Premium | Spigot/Paper Expert',
|
||||||
description: 'Développement de plugin Minecraft personnalisé pour serveurs Spigot, Paper et Bukkit. Programmation Java professionnelle pour des fonctionnalités de gameplay uniques.',
|
description: 'Plugins Minecraft sur-mesure qui transforment votre serveur en expérience unique. Compatible 1.8 → 1.20+, optimisé pour gros serveurs (1000+ joueurs). Votre avantage concurrentiel !',
|
||||||
features: [
|
features: [
|
||||||
'Mécaniques de jeu personnalisées et mini-jeux',
|
'🎯 Gameplay révolutionnaire : donjons procéduraux, boss custom, sorts magiques',
|
||||||
'Systèmes économiques et intégrations de boutique',
|
'💰 Économie avancée : boutiques GUI, auction house, métiers avec XP',
|
||||||
'Systèmes de permissions et rangs avancés',
|
'🏆 Systèmes de progression : levels, skills, classes RPG personnalisables',
|
||||||
'GUI personnalisées et menus d\'inventaire',
|
'💾 Base de données optimisée MySQL/Redis pour performances maximales',
|
||||||
'Intégration base de données (MySQL/SQLite)',
|
'🌍 Multi-serveurs : BungeeCord/Velocity ready avec synchronisation'
|
||||||
'Optimisé pour les performances des gros serveurs',
|
|
||||||
'Compatible avec les dernières versions Minecraft'
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'telegram-bot': {
|
'telegram-bot': {
|
||||||
title: 'Services de Développement de Bot Telegram',
|
title: '💬 Bot Telegram Pro Business | Automatisation Puissante',
|
||||||
description: 'Création professionnelle de bot Telegram avec claviers inline, réponses automatisées et intégrations API. Parfait pour les entreprises et communautés.',
|
description: 'Bot Telegram professionnel qui booste votre business. Parfait pour e-commerce, support client, communautés. Interface moderne, 0 code requis pour l\'utilisation !',
|
||||||
features: [
|
features: [
|
||||||
'Commandes personnalisées et claviers inline',
|
'🤖 IA conversationnelle : ChatGPT intégré pour réponses naturelles',
|
||||||
'Messagerie automatisée et diffusion',
|
'🛒 E-commerce complet : catalogue produits, panier, paiements Stripe/PayPal',
|
||||||
'Gestion des utilisateurs et analyses',
|
'📢 Broadcasting intelligent : segments utilisateurs, A/B testing, analytics',
|
||||||
'Intégration de paiement (Stripe, PayPal)',
|
'🌐 Multi-langues automatique avec détection et traduction DeepL',
|
||||||
'Support multi-langues',
|
'🔐 Sécurité maximale : 2FA, encryption, RGPD compliant'
|
||||||
'Support webhook et long polling',
|
|
||||||
'Panneau admin pour gestion du bot'
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'website-development': {
|
'website-development': {
|
||||||
title: 'Développement de Site Web Moderne',
|
title: '🌟 Site Web Premium Vue.js/React | SEO-First & Ultra-Rapide',
|
||||||
description: 'Développement web professionnel utilisant Vue.js, React et Node.js. Design responsive, optimisation SEO et performances ultra-rapides.',
|
description: 'Sites web nouvelle génération qui convertissent. Design premium, performance maximale, SEO optimisé. Votre concurrent #1 sur Google en 90 jours ou remboursé !',
|
||||||
features: [
|
features: [
|
||||||
'Design responsive pour tous les appareils',
|
'🎨 Design UI/UX premium : mockups Figma + animations modernes',
|
||||||
'Optimisation SEO et Core Web Vitals',
|
'⚡ Performance extrême : chargement <1.5s',
|
||||||
'UI/UX moderne avec animations fluides',
|
'📱 Responsive parfait : testé sur 50+ appareils différents',
|
||||||
'Intégration API et développement backend',
|
'🔍 SEO surpuissant : schema markup, sitemap, meta optimisées',
|
||||||
'E-commerce et traitement des paiements',
|
'🛒 E-commerce ready : Stripe, PayPal, cryptos (si besoin)'
|
||||||
'Système de Gestion de Contenu (CMS)',
|
|
||||||
'Configuration hébergement gratuite et déploiement'
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
testimonials: {
|
testimonials: {
|
||||||
title: 'Avis Clients & Témoignages',
|
title: '🌟 Ils Ont Transformé Leur Business Avec Mes Services',
|
||||||
subtitle: 'Rejoignez des centaines de clients satisfaits qui font confiance à mes services de développement. Taux de satisfaction de 100% avec des avis 5 étoiles.'
|
subtitle: 'Rejoignez 500+ entrepreneurs satisfaits. Note moyenne 5.0/5.0 sur l\'ensemble de mes services. La qualité parle d\'elle-même !'
|
||||||
},
|
},
|
||||||
cta: {
|
cta: {
|
||||||
title: 'Prêt à Démarrer Votre Projet ?',
|
title: '🎯 Arrêtez de Chercher, Vous Avez Trouvé LE Bon Développeur',
|
||||||
subtitle: 'Services de développement professionnels avec livraison rapide, révisions illimitées et support continu. Donnons vie à vos idées.',
|
subtitle: '⏰ Chaque jour sans agir = opportunités perdues. Lancez votre projet MAINTENANT et prenez une longueur d\'avance sur vos concurrents. Places limitées ce mois-ci !',
|
||||||
button: 'Parcourir Tous les Services sur Fiverr'
|
button: '🔥 Réserver Ma Commande Maintenant'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -264,34 +272,57 @@ export default {
|
|||||||
// Project data
|
// Project data
|
||||||
projectData: {
|
projectData: {
|
||||||
'virtual-tour': {
|
'virtual-tour': {
|
||||||
title: 'Plateforme Virtual Tour - Expérience 3D Interactive',
|
title: 'Visite Virtuelle - Expérience 360° Interactive',
|
||||||
description: 'Plateforme de visite virtuelle interactive construite avec Vue.js et Three.js. Expériences 3D immersives pour l\'immobilier, les musées et les entreprises.',
|
description: 'Mon professeur de lycée et moi avons eu l\'idée de créer une visite virtuelle avec des vidéos 360° pour permettre à tous de visiter l\'école depuis le web.',
|
||||||
longDescription: 'Plateforme de visite virtuelle avancée avec panoramas à 360 degrés, points chauds interactifs et navigation fluide. Construite avec Vue.js pour le frontend, Three.js pour le rendu 3D et backend Node.js. Optimisée pour les performances avec lazy loading et accélération WebGL. Parfaite pour les présentations immobilières, musées virtuels et visites d\'entreprise.'
|
longDescription: 'Projet collaboratif avec mon professeur de lycée pour créer une expérience de visite virtuelle immersive de notre établissement. Utilise des vidéos 360° pour offrir une navigation interactive et permettre aux futurs étudiants et parents de découvrir les installations scolaires à distance. Interface intuitive permettant d\'explorer les différents espaces : salles de classe, laboratoires, espaces communs et installations sportives.',
|
||||||
|
buttons: {
|
||||||
|
visit: 'Visiter'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'xinko': {
|
'xinko': {
|
||||||
title: 'Xinko - Framework Bot Multi-Plateforme',
|
title: 'Xinko - Bot Discord Polyvalent',
|
||||||
description: 'Framework bot polyvalent supportant Discord, Telegram et Slack. Construit avec Node.js et TypeScript pour un développement de bot évolutif.',
|
description: 'Xinko est un bot polyvalent qui peut vous aider à créer et gérer vos serveurs Discord avec facilité et plaisir. Il possède de nombreuses commandes et fonctionnalités.',
|
||||||
longDescription: 'Xinko est un framework bot multi-plateforme puissant conçu pour les développeurs. Les fonctionnalités incluent une API unifiée sur toutes les plateformes, système de plugins, abstraction de base de données et documentation complète. Construit avec Node.js, TypeScript et pratiques JavaScript modernes. Supporte Discord.js, Telegram Bot API et Slack SDK avec une seule base de code.'
|
longDescription: 'Bot Discord complet conçu pour simplifier la gestion des serveurs. Xinko offre une large gamme de commandes pour la modération, le divertissement, l\'utilitaire et la gestion communautaire. Interface conviviale avec système de permissions avancé, commandes slash modernes et intégration avec diverses APIs. Parfait pour les communautés de toutes tailles cherchant à automatiser et enrichir leur expérience Discord.',
|
||||||
|
buttons: {
|
||||||
|
invite: 'Inviter'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'image-manipulation': {
|
'image-manipulation': {
|
||||||
title: 'API Manipulation d\'Images - Package NPM',
|
title: 'Manipulation d\'Images - Package NPM',
|
||||||
description: 'Package NPM populaire pour la manipulation d\'images programmatique. Génération d\'images basée sur Canvas avec 100k+ téléchargements et communauté active.',
|
description: 'Discord Image Generation : Package NPM pour la manipulation d\'images basée sur le code. Initialement une API, maintenant open-source.',
|
||||||
longDescription: 'Bibliothèque complète de manipulation d\'images pour applications Node.js. Les fonctionnalités incluent génération de memes, filtres, effets, superposition de texte et conversion de format. Développé à l\'origine comme API REST, maintenant disponible en package NPM open-source. Utilisé par des bots Discord, applications web et outils d\'automatisation. Supporte JPG, PNG, GIF et WebP avec capacités de streaming.'
|
longDescription: 'Package NPM open-source pour la génération et manipulation d\'images programmatique. Développé initialement comme API propriétaire, puis rendu disponible à la communauté. Offre des fonctionnalités avancées de traitement d\'images : génération de memes, filtres, effets visuels et compositions personnalisées. Particulièrement populaire dans l\'écosystème des bots Discord pour créer du contenu visuel dynamique et interactif.',
|
||||||
|
buttons: {
|
||||||
|
repository: 'Dépôt',
|
||||||
|
'npm package': 'Package NPM'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'primate-web-admin': {
|
'primate-web-admin': {
|
||||||
title: 'Primate Web Admin - Outil de Déploiement Entreprise',
|
title: 'Primate Web Admin - Interface de Gestion',
|
||||||
description: 'Interface web moderne pour système de déploiement Primate. Déploiement et gestion de logiciels de qualité entreprise pour infrastructure Windows.',
|
description: 'Primate Web Admin est une interface Web pour gérer Primate qui est un outil de déploiement similaire à Munki pour Windows.',
|
||||||
longDescription: 'Panneau d\'administration web professionnel pour Primate, un outil de déploiement type Munki pour environnements Windows. Construit avec frontend Vue.js et backend API RESTful. Les fonctionnalités incluent gestion de packages, planification de déploiement, surveillance des clients et rapports détaillés. Conçu pour les administrateurs IT gérant de grands déploiements Windows avec contrôle d\'accès basé sur les rôles et journalisation d\'audit.'
|
longDescription: 'Interface d\'administration web moderne pour Primate, un système de déploiement de logiciels pour environnements Windows. Inspiré de Munki (solution macOS), Primate Web Admin offre une gestion centralisée des déploiements logiciels sur infrastructure Windows. Interface intuitive pour la gestion des packages, planification des mises à jour, surveillance des clients et génération de rapports détaillés.',
|
||||||
|
buttons: {}
|
||||||
},
|
},
|
||||||
'instagram-bot': {
|
'instagram-bot': {
|
||||||
title: 'Bot Instagram - Génération de Contenu Automatisée',
|
title: 'Bot Instagram - Automatisation Complète',
|
||||||
description: 'Bot Instagram riche en fonctionnalités avec commandes de génération d\'images. Construit avec Insta.js pour stories, posts et automatisation DM.',
|
description: 'Bot Instagram entièrement fonctionnel utilisant Insta.js par androz2091. Il possède de nombreuses commandes. Génère des images avec des commandes comme : !stonk ou !invert.',
|
||||||
longDescription: 'Bot d\'automatisation Instagram avancé développé avec le framework Insta.js. Comprend des commandes de génération d\'images personnalisées (!stonk, !invert, !meme), interactions stories, publication automatisée et gestion DM. Inclut limitation de débit, support proxy et fonctionnalités de sécurité du compte. Parfait pour les créateurs de contenu et gestionnaires de médias sociaux cherchant à automatiser leur présence Instagram.'
|
longDescription: 'Bot d\'automatisation Instagram développé avec la bibliothèque Insta.js d\'androz2091. Offre une gamme complète de fonctionnalités d\'automatisation : publication de contenu, interaction avec les followers, génération d\'images personnalisées et gestion des messages directs. Inclut des commandes spécialisées pour la création de memes et effets visuels (!stonk, !invert) ainsi que des outils de modération et d\'analyse des performances.',
|
||||||
|
buttons: {
|
||||||
|
repository: 'Dépôt'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'crowdin-status-bot': {
|
'crowdin-status-bot': {
|
||||||
title: 'Bot de Statut Crowdin - Suivi de Progression des Traductions',
|
title: 'Bot de Statut Crowdin - Suivi des Traductions',
|
||||||
description: 'Bot Discord pour surveillance en temps réel des traductions Crowdin. Mises à jour automatiques du statut et suivi de progression pour équipes de localisation.',
|
description: 'Un bot qui récupère le statut des traductions Crowdin et met à jour les messages Discord avec le dernier statut. Restez informé des progrès !',
|
||||||
longDescription: 'Bot Discord spécialisé qui s\'intègre avec l\'API Crowdin pour fournir des mises à jour de progression de traduction en temps réel. Les fonctionnalités incluent messages de statut automatisés, barres de progression, classements des contributeurs et notifications de jalons. Outil essentiel pour les projets open-source et équipes de localisation gérant des traductions dans plusieurs langues. Supporte webhooks et règles de notification personnalisées.'
|
longDescription: 'Bot Discord spécialisé dans le suivi automatique des projets de traduction Crowdin. Se connecte à l\'API Crowdin pour récupérer les statistiques de progression en temps réel et met à jour automatiquement les messages Discord avec les dernières informations. Outil essentiel pour les équipes de localisation et projets open-source multilingues, permettant de maintenir la communauté informée des avancées de traduction et d\'encourager la participation des contributeurs.',
|
||||||
|
buttons: {
|
||||||
|
repository: 'Dépôt'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'flowboard': {
|
||||||
|
title: 'FlowBoard - Clone de Trello ',
|
||||||
|
description: 'FlowBoard est une solution complète de gestion de projet pour rationaliser les tâches, la collaboration d\'équipe, la gestion des délais et le suivi des progrès avec des analyses détaillées.',
|
||||||
|
longDescription: 'FlowBoard révolutionne la collaboration d\'équipe et la gestion de projet avec sa suite complète d\'outils. Construite avec des technologies web modernes, elle offre une interface intuitive pour organiser les tâches, gérer les délais et suivre les progrès. La plateforme propose des tableaux personnalisables, une collaboration en temps réel, des analyses avancées et des outils de communication transparents. Parfaite pour les équipes de toutes tailles cherchant à booster leur productivité et rationaliser leurs processus de travail.',
|
||||||
|
buttons: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -334,24 +365,153 @@ export default {
|
|||||||
// SEO
|
// SEO
|
||||||
seo: {
|
seo: {
|
||||||
home: {
|
home: {
|
||||||
title: 'Killian - Développeur Full Stack | Expert Vue.js, React, Node.js',
|
title: 'Développeur Full Stack Freelance Vue.js React Node.js | +5 ans exp | Killian Dalcin',
|
||||||
description: 'Développeur Full Stack professionnel spécialisé en Vue.js, React, Node.js. Expert en applications web, bots Discord et logiciels personnalisés. Engagez-moi pour votre prochain projet.'
|
description: '⭐ Développeur Full Stack expert freelance. Création d\'applications web Vue.js/React performantes, bots Discord sur-mesure, API Node.js robustes. ✅ 100% clients satisfaits ✅ Devis gratuit 24h ✅ Livraison rapide'
|
||||||
},
|
},
|
||||||
projects: {
|
projects: {
|
||||||
title: 'Portfolio Développement Web - Killian | Projets Full Stack',
|
title: 'Portfolio Développeur Full Stack 2024 | 50+ Projets Vue.js React Node.js',
|
||||||
description: 'Parcourez mon portfolio d\'applications Vue.js, sites React, API Node.js et bots Discord. Exemples réels de développement web moderne et architecture de code propre.'
|
description: '🏆 Découvrez 50+ projets web réussis : applications Vue.js haute performance, plateformes React scalables, API Node.js, bots Discord. Études de cas détaillées avec ROI prouvé.'
|
||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
title: 'À propos de Killian - Développeur Full Stack Expérimenté',
|
title: 'Killian Dalcin - Expert Développeur Full Stack Vue.js React Node.js | Bio',
|
||||||
description: 'Découvrez mon expertise en Vue.js, React, Node.js et développement web moderne. Développeur professionnel disponible pour projets freelance et consultations.'
|
description: '👨💻 Développeur Full Stack senior avec +5 ans d\'expertise. Spécialiste Vue.js, React, Node.js. 50+ projets livrés, 100% satisfaction client. Découvrez mon parcours et mes compétences.'
|
||||||
},
|
},
|
||||||
contact: {
|
contact: {
|
||||||
title: 'Contacter Développeur Full Stack - Killian | Engager Développeur Web',
|
title: 'Contact Développeur Full Stack Freelance | Devis Gratuit Sous 24h',
|
||||||
description: 'Contactez-moi pour projets de développement web, applications Vue.js, sites React ou API Node.js. Consultation et estimation de projet gratuites disponibles.'
|
description: '📞 Contactez un expert développeur Full Stack pour votre projet web. Consultation gratuite, devis détaillé sous 24h. Vue.js, React, Node.js. Réponse garantie en <24h.'
|
||||||
},
|
},
|
||||||
fiverr: {
|
fiverr: {
|
||||||
title: 'Services Fiverr - Bot Discord & Développement Web | Killian',
|
title: 'Services Fiverr 5⭐ Bot Discord & Dev Web | Top Seller 2024',
|
||||||
description: 'Services freelance professionnels sur Fiverr. Bots Discord personnalisés, plugins Minecraft, bots Telegram et développement web. Vendeur top-rated avec satisfaction 100%.'
|
description: '🔥 Services Fiverr premium : Bots Discord sur-mesure dès 150€, plugins Minecraft Java, bots Telegram pro, sites web modernes. Top Rated Seller, 100% satisfaction, livraison express.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Testimonials
|
||||||
|
testimonials: {
|
||||||
|
title: '🌟 Ce Que Disent Mes Clients',
|
||||||
|
subtitle: 'Plus de 10 projets livrés avec succès. Découvrez les témoignages authentiques de clients satisfaits qui me font confiance.',
|
||||||
|
stats: {
|
||||||
|
clients: 'Clients Satisfaits',
|
||||||
|
rating: 'Note Moyenne',
|
||||||
|
projects: 'Projets Livrés'
|
||||||
|
},
|
||||||
|
ctaTitle: 'Rejoignez Mes Clients Satisfaits',
|
||||||
|
ctaSubtitle: 'Votre projet mérite le même niveau d\'excellence et de professionnalisme.',
|
||||||
|
ctaText: '🚀 Démarrer Mon Projet',
|
||||||
|
reviewsLink: 'https://www.fiverr.com/mr_kayjaydee',
|
||||||
|
reviewsText: 'Voir Tous les Avis',
|
||||||
|
card: {
|
||||||
|
featured: 'Témoignage Vedette',
|
||||||
|
results: 'Résultats obtenus :'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// FAQ Component
|
||||||
|
faq: {
|
||||||
|
title: '❓ Questions Fréquentes',
|
||||||
|
subtitle: 'Trouvez rapidement les réponses à vos questions les plus courantes',
|
||||||
|
keyPoints: 'Points clés :',
|
||||||
|
// Home page FAQ questions and answers
|
||||||
|
homeFaq: {
|
||||||
|
delivery: {
|
||||||
|
question: 'Quels sont vos délais de livraison typiques ?',
|
||||||
|
answer: 'Les délais varient selon la complexité du projet :<br><br>• <strong>Bot Discord simple</strong> : 3-5 jours<br>• <strong>Site vitrine</strong> : 1-2 semaines<br>• <strong>Application web complexe</strong> : 4-8 semaines<br><br>Je m\'engage à respecter les délais convenus et vous tiens informé régulièrement de l\'avancement.',
|
||||||
|
features: [
|
||||||
|
'Planning détaillé fourni',
|
||||||
|
'Mises à jour quotidiennes',
|
||||||
|
'Livraison souvent en avance'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
maintenance: {
|
||||||
|
question: 'Proposez-vous de la maintenance après livraison ?',
|
||||||
|
answer: 'Absolument ! Chaque projet inclut une période de maintenance gratuite. Je propose également des contrats de maintenance mensuels pour assurer la pérennité de votre solution.',
|
||||||
|
features: [
|
||||||
|
'Support gratuit selon le package',
|
||||||
|
'Mises à jour de sécurité',
|
||||||
|
'Monitoring 24/7 disponible'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
companies: {
|
||||||
|
question: 'Travaillez-vous avec des entreprises de toutes tailles ?',
|
||||||
|
answer: 'Oui ! De la startup au grand groupe, j\'adapte mes services à vos besoins et votre budget. Chaque projet bénéficie du même niveau d\'excellence.',
|
||||||
|
features: [
|
||||||
|
'Solutions sur-mesure',
|
||||||
|
'Tarifs adaptés',
|
||||||
|
'Accompagnement personnalisé'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pricing: {
|
||||||
|
title: 'Choisissez votre plan de formation',
|
||||||
|
subtitle: 'Accédez à une formation complète en développement web avec nos plans flexibles adaptés à tous les niveaux',
|
||||||
|
monthly: 'Mensuel',
|
||||||
|
annual: 'Annuel',
|
||||||
|
mostPopular: 'Le plus populaire',
|
||||||
|
startTrial: 'Commencer l\'essai gratuit',
|
||||||
|
trialInfo: '14 jours d\'essai gratuit, puis facturation',
|
||||||
|
plans: {
|
||||||
|
starter: {
|
||||||
|
name: 'Débutant',
|
||||||
|
description: 'Parfait pour commencer votre parcours en développement web'
|
||||||
|
},
|
||||||
|
pro: {
|
||||||
|
name: 'Pro',
|
||||||
|
description: 'Idéal pour les développeurs qui veulent accélérer leur apprentissage'
|
||||||
|
},
|
||||||
|
expert: {
|
||||||
|
name: 'Expert',
|
||||||
|
description: 'Pour ceux qui visent l\'excellence et une carrière professionnelle'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
basicCourses: 'Accès aux cours de base (HTML, CSS, JavaScript)',
|
||||||
|
communityAccess: 'Accès à la communauté d\'apprentissage',
|
||||||
|
mobileApp: 'Application mobile pour apprendre partout',
|
||||||
|
basicSupport: 'Support par email',
|
||||||
|
certificates: 'Certificats de completion',
|
||||||
|
allCourses: 'Accès à tous les cours et technologies',
|
||||||
|
liveWorkshops: 'Ateliers en direct chaque semaine',
|
||||||
|
mentorship: 'Sessions de mentorat mensuel',
|
||||||
|
prioritySupport: 'Support prioritaire 24/7',
|
||||||
|
jobBoard: 'Accès au tableau d\'emploi exclusif',
|
||||||
|
portfolioReview: 'Révision de portfolio par des experts',
|
||||||
|
everythingPro: 'Tout du plan Pro inclus',
|
||||||
|
oneOnOneCoaching: 'Coaching individuel hebdomadaire',
|
||||||
|
customProjects: 'Projets personnalisés selon vos objectifs',
|
||||||
|
internshipPlacement: 'Aide au placement en stage',
|
||||||
|
careerGuidance: 'Conseils carrière personnalisés',
|
||||||
|
exclusiveContent: 'Contenu exclusif et avant-première',
|
||||||
|
networkingEvents: 'Événements de networking avec les pros'
|
||||||
|
},
|
||||||
|
faq: {
|
||||||
|
title: 'Questions fréquentes',
|
||||||
|
items: {
|
||||||
|
trial: {
|
||||||
|
question: 'Comment fonctionne l\'essai gratuit ?',
|
||||||
|
answer: 'Profitez de 14 jours d\'accès complet à votre plan choisi. Aucun engagement, vous pouvez annuler à tout moment pendant la période d\'essai.'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
question: 'Puis-je annuler mon abonnement à tout moment ?',
|
||||||
|
answer: 'Oui, vous pouvez annuler votre abonnement à tout moment depuis votre tableau de bord. L\'accès reste actif jusqu\'à la fin de votre période de facturation.'
|
||||||
|
},
|
||||||
|
refund: {
|
||||||
|
question: 'Offrez-vous une garantie de remboursement ?',
|
||||||
|
answer: 'Nous offrons une garantie de remboursement de 30 jours si vous n\'êtes pas satisfait de votre formation.'
|
||||||
|
},
|
||||||
|
upgrade: {
|
||||||
|
question: 'Puis-je changer de plan en cours d\'abonnement ?',
|
||||||
|
answer: 'Absolument ! Vous pouvez passer à un plan supérieur à tout moment. La différence sera calculée au prorata.'
|
||||||
|
},
|
||||||
|
certificates: {
|
||||||
|
question: 'Les certificats sont-ils reconnus ?',
|
||||||
|
answer: 'Nos certificats sont reconnus par de nombreuses entreprises tech et peuvent être ajoutés à votre profil LinkedIn.'
|
||||||
|
},
|
||||||
|
support: {
|
||||||
|
question: 'Quel type de support est disponible ?',
|
||||||
|
answer: 'Selon votre plan, vous avez accès au support par email, chat en direct, ou sessions de mentorat personnalisées.'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,15 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory, type RouteLocationNormalized } from 'vue-router'
|
||||||
import { nextTick } from 'vue'
|
import { nextTick } from 'vue'
|
||||||
import HomePage from '../views/HomePage.vue'
|
import HomePage from '../views/HomePage.vue'
|
||||||
|
|
||||||
|
// Google Analytics gtag types
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
dataLayer: unknown[]
|
||||||
|
gtag: (...args: unknown[]) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
@@ -34,6 +42,21 @@ const router = createRouter({
|
|||||||
path: '/fiverr',
|
path: '/fiverr',
|
||||||
name: 'fiverr',
|
name: 'fiverr',
|
||||||
component: () => import('../views/FiverrPage.vue')
|
component: () => import('../views/FiverrPage.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/formation',
|
||||||
|
name: 'formation',
|
||||||
|
component: () => import('../views/FormationPage.vue')
|
||||||
|
},
|
||||||
|
// TODO: page 404
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
name: 'not-found',
|
||||||
|
component: HomePage,
|
||||||
|
meta: {
|
||||||
|
title: 'Page non trouvée - 404',
|
||||||
|
description: 'La page que vous recherchez n\'existe pas. Retournez à l\'accueil pour découvrir mes services.'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
scrollBehavior(to, from, savedPosition) {
|
scrollBehavior(to, from, savedPosition) {
|
||||||
@@ -58,9 +81,41 @@ const router = createRouter({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Additional scroll to top handler for better compatibility
|
// SEO Meta tags handler
|
||||||
router.afterEach(() => {
|
router.beforeEach((to, from, next) => {
|
||||||
// Use nextTick to ensure the DOM is fully updated
|
// Update document title
|
||||||
|
if (to.meta?.title) {
|
||||||
|
document.title = to.meta.title as string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update meta description
|
||||||
|
if (to.meta?.description) {
|
||||||
|
let metaDescription = document.querySelector('meta[name="description"]')
|
||||||
|
if (!metaDescription) {
|
||||||
|
metaDescription = document.createElement('meta')
|
||||||
|
metaDescription.setAttribute('name', 'description')
|
||||||
|
document.head.appendChild(metaDescription)
|
||||||
|
}
|
||||||
|
metaDescription.setAttribute('content', to.meta.description as string)
|
||||||
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Google Analytics page view tracking function
|
||||||
|
const trackPageView = (route: RouteLocationNormalized) => {
|
||||||
|
// Track page view with gtag
|
||||||
|
if (window.gtag) {
|
||||||
|
window.gtag('config', 'G-CDVVNFY6MV', {
|
||||||
|
page_path: route.path,
|
||||||
|
page_title: document.title,
|
||||||
|
page_location: window.location.href
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track page views on route changes
|
||||||
|
router.afterEach((to) => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// Smooth scroll to top
|
// Smooth scroll to top
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
@@ -68,7 +123,16 @@ router.afterEach(() => {
|
|||||||
left: 0,
|
left: 0,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Track page view for GTM
|
||||||
|
trackPageView(to)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Track initial page load
|
||||||
|
router.isReady().then(() => {
|
||||||
|
// Track the initial route after router is ready
|
||||||
|
trackPageView(router.currentRoute.value)
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
|||||||
import { useSeo } from '@/composables/useSeo'
|
import { useSeo } from '@/composables/useSeo'
|
||||||
import { useI18n } from '@/composables/useI18n'
|
import { useI18n } from '@/composables/useI18n'
|
||||||
import TechBadge from '@/components/TechBadge.vue'
|
import TechBadge from '@/components/TechBadge.vue'
|
||||||
|
import SectionCTA from '@/components/shared/SectionCTA.vue'
|
||||||
import { techStack } from '@/data/techstack'
|
import { techStack } from '@/data/techstack'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -90,7 +91,7 @@ const approachCards = computed(() => [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main class="page-enter">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -201,29 +202,9 @@ const approachCards = computed(() => [
|
|||||||
<!-- CTA Section -->
|
<!-- CTA Section -->
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="text-center">
|
<SectionCTA :question="t('about.cta.title')" :description="t('about.cta.description')"
|
||||||
<h2 class="mb-lg">{{ t('about.cta.title') }}</h2>
|
:primary-text="t('about.cta.button')" primary-link="/contact" :secondary-text="t('home.cta.viewProjects')"
|
||||||
<p class="text-xl text-secondary max-w-2xl mx-auto mb-2xl">
|
secondary-link="/projects" />
|
||||||
{{ t('about.cta.description') }}
|
|
||||||
</p>
|
|
||||||
<div class="flex flex-col sm:flex-row gap-md justify-center">
|
|
||||||
<RouterLink to="/contact" class="btn btn-primary btn-lg">
|
|
||||||
{{ t('about.cta.button') }}
|
|
||||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
||||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z">
|
|
||||||
</path>
|
|
||||||
</svg>
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink to="/projects" class="btn btn-secondary 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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
@@ -48,11 +48,11 @@ useSeo({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="contact-page">
|
<main class="contact-page page-enter">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="contact-hero">
|
<section class="contact-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-content text-center">
|
<div class="hero-content text-center animate-fade-in-up">
|
||||||
<h1 class="hero-title">{{ t('contact.title') }}</h1>
|
<h1 class="hero-title">{{ t('contact.title') }}</h1>
|
||||||
<p class="hero-subtitle">
|
<p class="hero-subtitle">
|
||||||
{{ t('contact.subtitle') }}
|
{{ t('contact.subtitle') }}
|
||||||
@@ -82,7 +82,7 @@ useSeo({
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2xl max-w-4xl mx-auto">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2xl max-w-4xl mx-auto">
|
||||||
<!-- Contact Methods -->
|
<!-- Contact Methods -->
|
||||||
<div class="card">
|
<div class="card animate-fade-in-up" style="animation-delay: 0.2s;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="text-2xl font-bold mb-lg">{{ t('contact.quickContact') }}</h2>
|
<h2 class="text-2xl font-bold mb-lg">{{ t('contact.quickContact') }}</h2>
|
||||||
<div class="space-y-md">
|
<div class="space-y-md">
|
||||||
@@ -101,7 +101,7 @@ useSeo({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Social Links -->
|
<!-- Social Links -->
|
||||||
<div class="card">
|
<div class="card animate-fade-in-up" style="animation-delay: 0.3s;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="text-2xl font-bold mb-lg">{{ t('contact.findMeOn') }}</h2>
|
<h2 class="text-2xl font-bold mb-lg">{{ t('contact.findMeOn') }}</h2>
|
||||||
<div class="space-y-sm">
|
<div class="space-y-sm">
|
||||||
@@ -142,7 +142,7 @@ useSeo({
|
|||||||
<!-- FAQ Section -->
|
<!-- FAQ Section -->
|
||||||
<section class="faq-section">
|
<section class="faq-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="text-center mb-2xl">
|
<div class="text-center mb-2xl animate-fade-in-up" style="animation-delay: 0.4s;">
|
||||||
<h2 class="mb-lg">{{ t('contact.faq.title') }}</h2>
|
<h2 class="mb-lg">{{ t('contact.faq.title') }}</h2>
|
||||||
<p class="text-xl text-secondary max-w-2xl mx-auto">
|
<p class="text-xl text-secondary max-w-2xl mx-auto">
|
||||||
{{ t('contact.faq.subtitle') }}
|
{{ t('contact.faq.subtitle') }}
|
||||||
@@ -150,7 +150,7 @@ useSeo({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-xl">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-xl">
|
||||||
<div class="card text-center">
|
<div class="card text-center animate-fade-in-up" style="animation-delay: 0.5s;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="faq-icon faq-icon-success">
|
<div class="faq-icon faq-icon-success">
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -163,7 +163,7 @@ useSeo({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card text-center">
|
<div class="card text-center animate-fade-in-up" style="animation-delay: 0.6s;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="faq-icon faq-icon-primary">
|
<div class="faq-icon faq-icon-primary">
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -177,7 +177,7 @@ useSeo({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card text-center">
|
<div class="card text-center animate-fade-in-up" style="animation-delay: 0.7s;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="faq-icon faq-icon-secondary">
|
<div class="faq-icon faq-icon-secondary">
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
@@ -20,33 +20,42 @@ useSeo({
|
|||||||
description: t('seo.fiverr.description'),
|
description: t('seo.fiverr.description'),
|
||||||
keywords: 'fiverr services, discord bot development, minecraft plugin development, telegram bot creation, web development services, freelance developer, custom bot development, fiverr gigs',
|
keywords: 'fiverr services, discord bot development, minecraft plugin development, telegram bot creation, web development services, freelance developer, custom bot development, fiverr gigs',
|
||||||
ogImage: '/portfolio-preview.webp',
|
ogImage: '/portfolio-preview.webp',
|
||||||
structuredData: {
|
structuredData: [
|
||||||
'@context': 'https://schema.org',
|
{
|
||||||
'@type': 'Service',
|
'@context': 'https://schema.org',
|
||||||
'name': 'Professional Development Services on Fiverr',
|
'@type': 'Service',
|
||||||
'description': 'Custom Discord bot development, Minecraft plugins, Telegram bots, and web development services',
|
'name': 'Professional Development Services on Fiverr',
|
||||||
'provider': {
|
'description': 'Custom Discord bot development, Minecraft plugins, Telegram bots, and web development services',
|
||||||
'@type': 'Person',
|
'url': 'https://killiandalcin.fr/fiverr',
|
||||||
'name': 'Killian',
|
'provider': {
|
||||||
'jobTitle': 'Full Stack Developer'
|
'@type': 'Person',
|
||||||
|
'name': 'Killian',
|
||||||
|
'jobTitle': 'Full Stack Developer',
|
||||||
|
'url': 'https://killiandalcin.fr'
|
||||||
|
},
|
||||||
|
'offers': siteConfig.value.fiverr.services.map(service => ({
|
||||||
|
'@type': 'Offer',
|
||||||
|
'name': t(`fiverr.serviceData.${service.id}.title`),
|
||||||
|
'description': t(`fiverr.serviceData.${service.id}.description`),
|
||||||
|
'price': service.price.replace('$', ''),
|
||||||
|
'priceCurrency': 'USD',
|
||||||
|
'url': service.url,
|
||||||
|
'availability': service.url !== '#' ? 'https://schema.org/InStock' : 'https://schema.org/PreOrder'
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
'offers': siteConfig.value.fiverr.services.map(service => ({
|
{
|
||||||
'@type': 'Offer',
|
|
||||||
'name': t(`fiverr.serviceData.${service.id}.title`),
|
|
||||||
'description': t(`fiverr.serviceData.${service.id}.description`),
|
|
||||||
'price': service.price.replace('$', ''),
|
|
||||||
'priceCurrency': 'USD',
|
|
||||||
'url': service.url,
|
|
||||||
'availability': service.url !== '#' ? 'https://schema.org/InStock' : 'https://schema.org/PreOrder'
|
|
||||||
})),
|
|
||||||
'aggregateRating': {
|
|
||||||
'@type': 'AggregateRating',
|
'@type': 'AggregateRating',
|
||||||
'ratingValue': '5',
|
'itemReviewed': {
|
||||||
'bestRating': '5',
|
'@type': 'LocalBusiness',
|
||||||
'worstRating': '1',
|
'name': 'Professional Development Services on Fiverr'
|
||||||
'reviewCount': '50+'
|
},
|
||||||
|
'ratingValue': 5,
|
||||||
|
'bestRating': 5,
|
||||||
|
'worstRating': 1,
|
||||||
|
'ratingCount': 50,
|
||||||
|
'reviewCount': 50
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const services = computed(() => siteConfig.value.fiverr.services)
|
const services = computed(() => siteConfig.value.fiverr.services)
|
||||||
@@ -90,30 +99,37 @@ const getServiceCardProps = (service: { id: string; price: string; url: string;
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="fiverr-page">
|
<main class="fiverr-page page-enter">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<FiverrHero :title="t('fiverr.title')" :subtitle="t('fiverr.subtitle')" :stats="heroStats"
|
<div class="animate-fade-in-up">
|
||||||
:cta-url="siteConfig.fiverr.profileUrl" :cta-text="t('fiverr.profileCta')" />
|
<FiverrHero :title="t('fiverr.title')" :subtitle="t('fiverr.subtitle')" :stats="heroStats"
|
||||||
|
:cta-url="siteConfig.fiverr.profileUrl" :cta-text="t('fiverr.profileCta')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Services Section -->
|
<!-- Services Section -->
|
||||||
<section class="services-grid-section">
|
<section class="services-grid-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- Section Header -->
|
<!-- Section Header -->
|
||||||
<div class="section-header text-center">
|
<div class="section-header text-center animate-fade-in-up" style="animation-delay: 0.2s;">
|
||||||
<h2 class="section-title">{{ t('fiverr.services.title') }}</h2>
|
<h2 class="section-title">{{ t('fiverr.services.title') }}</h2>
|
||||||
<p class="section-subtitle">{{ t('fiverr.services.subtitle') }}</p>
|
<p class="section-subtitle">{{ t('fiverr.services.subtitle') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Services Grid -->
|
<!-- Services Grid -->
|
||||||
<div class="services-grid">
|
<div class="services-grid">
|
||||||
<FiverrServiceCard v-for="service in services" :key="service.id" v-bind="getServiceCardProps(service)" />
|
<div v-for="(service, index) in services" :key="service.id" class="animate-fade-in-up"
|
||||||
|
:style="{ 'animation-delay': `${0.3 + index * 0.1}s` }">
|
||||||
|
<FiverrServiceCard v-bind="getServiceCardProps(service)" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- CTA Section -->
|
<!-- CTA Section -->
|
||||||
<FiverrCta :title="t('fiverr.cta.title')" :subtitle="t('fiverr.cta.subtitle')"
|
<div class="animate-fade-in-up" style="animation-delay: 0.4s;">
|
||||||
:cta-url="siteConfig.fiverr.profileUrl" :cta-text="t('fiverr.cta.button')" />
|
<FiverrCta :title="t('fiverr.cta.title')" :subtitle="t('fiverr.cta.subtitle')"
|
||||||
|
:cta-url="siteConfig.fiverr.profileUrl" :cta-text="t('fiverr.cta.button')" />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
170
src/views/FormationPage.vue
Normal file
170
src/views/FormationPage.vue
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<template>
|
||||||
|
<div class="formation-page page-enter">
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<section class="hero-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="hero-content animate-fade-in-up">
|
||||||
|
<h1 class="hero-title">{{ $t('pricing.title') }}</h1>
|
||||||
|
<p class="hero-subtitle">{{ $t('pricing.subtitle') }}</p>
|
||||||
|
|
||||||
|
<!-- Billing Toggle -->
|
||||||
|
<div class="billing-toggle">
|
||||||
|
<span :class="{ active: billingType === 'monthly' }">{{ $t('pricing.monthly') }}</span>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" v-model="isAnnual" @change="toggleBilling">
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
<span :class="{ active: billingType === 'annual' }">
|
||||||
|
{{ $t('pricing.annual') }}
|
||||||
|
<span class="discount-badge">-20%</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Pricing Cards -->
|
||||||
|
<section class="pricing-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="pricing-grid">
|
||||||
|
<div v-for="(plan, index) in pricingPlans" :key="plan.id" :class="['pricing-card', { popular: plan.popular }]"
|
||||||
|
class="animate-fade-in-up" :style="{ 'animation-delay': `${index * 0.1}s` }">
|
||||||
|
<div v-if="plan.popular" class="popular-badge">
|
||||||
|
{{ $t('pricing.mostPopular') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="plan-name">{{ $t(`pricing.plans.${plan.id}.name`) }}</h3>
|
||||||
|
<div class="plan-price">
|
||||||
|
<span class="currency">€</span>
|
||||||
|
<span class="amount">{{ getCurrentPrice(plan) }}</span>
|
||||||
|
<span class="period">/{{ billingType === 'monthly' ? 'mois' : 'an' }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="plan-description">{{ $t(`pricing.plans.${plan.id}.description`) }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="features-list">
|
||||||
|
<li v-for="feature in plan.features" :key="feature" class="feature-item">
|
||||||
|
<svg class="check-icon" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
{{ $t(`pricing.features.${feature}`) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="card-actions">
|
||||||
|
<button :class="['cta-button', plan.popular ? 'primary' : 'secondary']">
|
||||||
|
{{ $t('pricing.startTrial') }}
|
||||||
|
</button>
|
||||||
|
<p class="trial-info">{{ $t('pricing.trialInfo') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- FAQ Section -->
|
||||||
|
<section class="faq-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="section-title animate-fade-in-up">{{ $t('pricing.faq.title') }}</h2>
|
||||||
|
<div class="faq-grid">
|
||||||
|
<div v-for="(faq, index) in pricingFAQ" :key="faq.id" class="faq-item animate-fade-in-up"
|
||||||
|
:style="{ 'animation-delay': `${index * 0.1}s` }">
|
||||||
|
<h3 class="faq-question">{{ $t(`pricing.faq.items.${faq.id}.question`) }}</h3>
|
||||||
|
<p class="faq-answer">{{ $t(`pricing.faq.items.${faq.id}.answer`) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useSeo } from '@/composables/useSeo'
|
||||||
|
|
||||||
|
// SEO
|
||||||
|
useSeo({
|
||||||
|
title: 'Tarifs - Formation Développement Web',
|
||||||
|
description: 'Découvrez nos plans de formation en développement web. Choisissez l\'abonnement qui vous convient avec des prix flexibles mensuels ou annuels.',
|
||||||
|
keywords: 'formation développement web, tarifs, abonnement, cours en ligne, prix'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Billing state
|
||||||
|
const isAnnual = ref(false)
|
||||||
|
const billingType = computed(() => isAnnual.value ? 'annual' : 'monthly')
|
||||||
|
|
||||||
|
const toggleBilling = () => {
|
||||||
|
// The billingType is computed, no need to manually set it
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pricing plans
|
||||||
|
const pricingPlans = [
|
||||||
|
{
|
||||||
|
id: 'starter',
|
||||||
|
popular: false,
|
||||||
|
monthlyPrice: 29,
|
||||||
|
annualPrice: 279, // 20% discount
|
||||||
|
features: [
|
||||||
|
'basicCourses',
|
||||||
|
'communityAccess',
|
||||||
|
'mobileApp',
|
||||||
|
'basicSupport',
|
||||||
|
'certificates'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pro',
|
||||||
|
popular: true,
|
||||||
|
monthlyPrice: 59,
|
||||||
|
annualPrice: 567, // 20% discount
|
||||||
|
features: [
|
||||||
|
'allCourses',
|
||||||
|
'liveWorkshops',
|
||||||
|
'mentorship',
|
||||||
|
'prioritySupport',
|
||||||
|
'certificates',
|
||||||
|
'jobBoard',
|
||||||
|
'portfolioReview'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'expert',
|
||||||
|
popular: false,
|
||||||
|
monthlyPrice: 99,
|
||||||
|
annualPrice: 950, // 20% discount
|
||||||
|
features: [
|
||||||
|
'everythingPro',
|
||||||
|
'oneOnOneCoaching',
|
||||||
|
'customProjects',
|
||||||
|
'internshipPlacement',
|
||||||
|
'careerGuidance',
|
||||||
|
'exclusiveContent',
|
||||||
|
'networkingEvents'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// FAQ data
|
||||||
|
const pricingFAQ = [
|
||||||
|
{ id: 'trial' },
|
||||||
|
{ id: 'cancel' },
|
||||||
|
{ id: 'refund' },
|
||||||
|
{ id: 'upgrade' },
|
||||||
|
{ id: 'certificates' },
|
||||||
|
{ id: 'support' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// Helper function to get current price
|
||||||
|
const getCurrentPrice = (plan: { monthlyPrice: number; annualPrice: number }) => {
|
||||||
|
return billingType.value === 'monthly' ? plan.monthlyPrice : plan.annualPrice
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@import './styles/FormationPage.css';
|
||||||
|
</style>
|
@@ -2,10 +2,41 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useSeo } from '@/composables/useSeo'
|
import { useSeo } from '@/composables/useSeo'
|
||||||
import { useI18n } from '@/composables/useI18n'
|
import { useI18n } from '@/composables/useI18n'
|
||||||
import { projects } from '@/data/projects'
|
import HeroSection from '@/components/sections/HeroSection.vue'
|
||||||
import ProjectCard from '@/components/ProjectCard.vue'
|
import FeaturedProjectsSection from '@/components/sections/FeaturedProjectsSection.vue'
|
||||||
|
import ServicesSection from '@/components/sections/ServicesSection.vue'
|
||||||
|
import TestimonialsSection from '@/components/TestimonialsSection.vue'
|
||||||
|
import ServiceFAQ from '@/components/ServiceFAQ.vue'
|
||||||
|
import CTASection from '@/components/sections/CTASection.vue'
|
||||||
|
import { testimonials, testimonialsStats } from '@/data/testimonials'
|
||||||
|
import frLocale from '@/locales/fr'
|
||||||
|
import enLocale from '@/locales/en'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t, currentLocale } = useI18n()
|
||||||
|
|
||||||
|
// Get translated FAQs using computed with direct locale access
|
||||||
|
const homeFAQs = computed(() => {
|
||||||
|
const locale = currentLocale.value === 'fr' ? frLocale : enLocale
|
||||||
|
const faqData = locale.faq.homeFaq
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
question: faqData.delivery.question,
|
||||||
|
answer: faqData.delivery.answer,
|
||||||
|
features: faqData.delivery.features
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: faqData.maintenance.question,
|
||||||
|
answer: faqData.maintenance.answer,
|
||||||
|
features: faqData.maintenance.features
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: faqData.companies.question,
|
||||||
|
answer: faqData.companies.answer,
|
||||||
|
features: faqData.companies.features
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
// Enhanced SEO with structured data
|
// Enhanced SEO with structured data
|
||||||
useSeo({
|
useSeo({
|
||||||
@@ -36,168 +67,33 @@ useSeo({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Featured projects
|
|
||||||
const featuredProjects = computed(() => {
|
|
||||||
return projects.filter(project => project.featured).slice(0, 3)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main class="page-enter">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="hero">
|
<HeroSection />
|
||||||
<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>
|
|
||||||
<div class="flex flex-col sm:flex-row gap-md justify-center">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Featured Projects Section -->
|
<!-- Featured Projects Section -->
|
||||||
<section class="section">
|
<FeaturedProjectsSection />
|
||||||
<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="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-xl mb-2xl">
|
|
||||||
<ProjectCard v-for="project in featuredProjects" :key="project.id" :project="project"
|
|
||||||
class="animate-fade-in-up" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Services Section -->
|
<!-- Services Section -->
|
||||||
<section class="services-section">
|
<ServicesSection />
|
||||||
<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="grid grid-cols-1 md:grid-cols-2 gap-xl">
|
<!-- Testimonials Section -->
|
||||||
<div v-for="(service, index) in services" :key="index" class="card animate-fade-in-up"
|
<TestimonialsSection :title="t('testimonials.title')" :subtitle="t('testimonials.subtitle')"
|
||||||
:style="{ 'animation-delay': `${index * 0.1}s` }">
|
:testimonials="testimonials" :stats="testimonialsStats"
|
||||||
<div class="card-body">
|
:stats-labels="{ clients: t('testimonials.stats.clients'), rating: t('testimonials.stats.rating'), projects: t('testimonials.stats.projects') }"
|
||||||
<div class="flex items-start">
|
:cta-title="t('testimonials.ctaTitle')" :cta-subtitle="t('testimonials.ctaSubtitle')"
|
||||||
<div class="service-icon" :style="{ background: service.color, color: 'var(--text-inverse)' }">
|
:cta-text="t('testimonials.ctaText')" :cta-link="'/contact'" :reviews-link="t('testimonials.reviewsLink')"
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
:reviews-text="t('testimonials.reviewsText')" />
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="service.icon"></path>
|
|
||||||
</svg>
|
<!-- FAQ Section -->
|
||||||
</div>
|
<ServiceFAQ :title="t('faq.title')" :subtitle="t('faq.subtitle')" :faqs="homeFAQs" :cta-title="t('faq.ctaTitle')"
|
||||||
<div>
|
:cta-subtitle="t('faq.ctaSubtitle')" :cta-text="t('faq.ctaText')" :cta-link="'/contact'" />
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- CTA Section -->
|
<!-- CTA Section -->
|
||||||
<section class="section">
|
<CTASection />
|
||||||
<div class="container">
|
|
||||||
<div class="text-center">
|
|
||||||
<h2 class="mb-lg">{{ t('home.cta2.title') }}</h2>
|
|
||||||
<p class="text-xl text-secondary max-w-2xl mx-auto mb-2xl">
|
|
||||||
{{ t('home.cta2.subtitle') }}
|
|
||||||
</p>
|
|
||||||
<div class="flex flex-col sm:flex-row gap-md justify-center">
|
|
||||||
<RouterLink to="/contact" class="btn btn-primary btn-lg">
|
|
||||||
{{ t('home.cta2.startProject') }}
|
|
||||||
<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 to="/about" class="btn btn-secondary btn-lg">
|
|
||||||
{{ t('home.cta2.learnMore') }}
|
|
||||||
<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 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z">
|
|
||||||
</path>
|
|
||||||
</svg>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -3,24 +3,31 @@ import { computed, onMounted } from 'vue'
|
|||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useSeo } from '@/composables/useSeo'
|
import { useSeo } from '@/composables/useSeo'
|
||||||
import { useAssets } from '@/composables/useAssets'
|
import { useAssets } from '@/composables/useAssets'
|
||||||
import { projects } from '@/data/projects'
|
import { useProjects } from '@/composables/useProjects'
|
||||||
|
import { useI18n } from '@/composables/useI18n'
|
||||||
|
import { useGallery } from '@/composables/useGallery'
|
||||||
import TechBadge from '@/components/TechBadge.vue'
|
import TechBadge from '@/components/TechBadge.vue'
|
||||||
|
import GalleryModal from '@/components/GalleryModal.vue'
|
||||||
|
import CTAButtons from '@/components/shared/CTAButtons.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { getImageUrl } = useAssets()
|
const { getImageUrl } = useAssets()
|
||||||
|
const { projects } = useProjects()
|
||||||
|
const { t } = useI18n()
|
||||||
|
const gallery = useGallery()
|
||||||
|
|
||||||
// Find project by ID
|
// Find project by ID
|
||||||
const project = computed(() => {
|
const project = computed(() => {
|
||||||
const id = route.params.id as string
|
const id = route.params.id as string
|
||||||
return projects.find(p => p.id === id)
|
return projects.value.find(p => p.id === id)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Related projects
|
// Related projects
|
||||||
const relatedProjects = computed(() => {
|
const relatedProjects = computed(() => {
|
||||||
if (!project.value) return []
|
if (!project.value) return []
|
||||||
|
|
||||||
return projects
|
return projects.value
|
||||||
.filter(p => p.id !== project.value?.id && p.category === project.value?.category)
|
.filter(p => p.id !== project.value?.id && p.category === project.value?.category)
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
})
|
})
|
||||||
@@ -61,18 +68,18 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main v-if="project" class="project-detail-page">
|
<main v-if="project" class="project-detail-page page-enter">
|
||||||
<!-- Hero Section - Redesigned -->
|
<!-- Hero Section - Redesigned -->
|
||||||
<section class="project-hero">
|
<section class="project-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-content">
|
<div class="hero-content animate-fade-in-up">
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="breadcrumb">
|
<nav class="breadcrumb">
|
||||||
<button @click="goBack" class="breadcrumb-link">
|
<button @click="goBack" class="breadcrumb-link">
|
||||||
<svg class="breadcrumb-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="breadcrumb-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||||
</svg>
|
</svg>
|
||||||
Retour aux projets
|
{{ t('projects.projectDetail.backToProjects') }}
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -94,14 +101,14 @@ onMounted(() => {
|
|||||||
<p class="project-description">{{ project.description }}</p>
|
<p class="project-description">{{ project.description }}</p>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div class="project-actions">
|
<CTAButtons layout="row">
|
||||||
<a v-if="project.demoUrl" :href="project.demoUrl" target="_blank" rel="noopener noreferrer"
|
<a v-if="project.demoUrl" :href="project.demoUrl" target="_blank" rel="noopener noreferrer"
|
||||||
class="btn btn-primary">
|
class="btn btn-primary">
|
||||||
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<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>
|
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
||||||
</svg>
|
</svg>
|
||||||
Voir la démo
|
{{ t('projects.projectDetail.viewDemo') }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a v-if="project.githubUrl" :href="project.githubUrl" target="_blank" rel="noopener noreferrer"
|
<a v-if="project.githubUrl" :href="project.githubUrl" target="_blank" rel="noopener noreferrer"
|
||||||
@@ -110,7 +117,7 @@ onMounted(() => {
|
|||||||
<path
|
<path
|
||||||
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||||
</svg>
|
</svg>
|
||||||
Code source
|
{{ t('projects.projectDetail.sourceCode') }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Buttons from project data -->
|
<!-- Buttons from project data -->
|
||||||
@@ -129,9 +136,9 @@ onMounted(() => {
|
|||||||
d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z">
|
d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z">
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
Partager
|
{{ t('projects.projectDetail.share') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</CTAButtons>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,7 +153,7 @@ onMounted(() => {
|
|||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
<!-- Project Details -->
|
<!-- Project Details -->
|
||||||
<div class="content-section">
|
<div class="content-section">
|
||||||
<h2 class="section-title">À propos du projet</h2>
|
<h2 class="section-title">{{ t('projects.projectDetail.aboutProject') }}</h2>
|
||||||
<div class="section-content">
|
<div class="section-content">
|
||||||
<p class="project-long-description">
|
<p class="project-long-description">
|
||||||
{{ project.longDescription || project.description }}
|
{{ project.longDescription || project.description }}
|
||||||
@@ -154,7 +161,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- Features -->
|
<!-- Features -->
|
||||||
<div v-if="project.features" class="features-list">
|
<div v-if="project.features" class="features-list">
|
||||||
<h3 class="features-title">Fonctionnalités principales</h3>
|
<h3 class="features-title">{{ t('projects.projectDetail.keyFeatures') }}</h3>
|
||||||
<ul class="features">
|
<ul class="features">
|
||||||
<li v-for="feature in project.features" :key="feature" class="feature-item">
|
<li v-for="feature in project.features" :key="feature" class="feature-item">
|
||||||
<svg class="feature-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="feature-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -169,7 +176,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- Technologies -->
|
<!-- Technologies -->
|
||||||
<div v-if="project.technologies" class="content-section">
|
<div v-if="project.technologies" class="content-section">
|
||||||
<h2 class="section-title">Technologies utilisées</h2>
|
<h2 class="section-title">{{ t('projects.projectDetail.technologiesUsed') }}</h2>
|
||||||
<div class="section-content">
|
<div class="section-content">
|
||||||
<div class="tech-grid">
|
<div class="tech-grid">
|
||||||
<TechBadge v-for="tech in project.technologies" :key="tech" :tech="tech" class="tech-item" />
|
<TechBadge v-for="tech in project.technologies" :key="tech" :tech="tech" class="tech-item" />
|
||||||
@@ -179,11 +186,21 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- Gallery -->
|
<!-- Gallery -->
|
||||||
<div v-if="project.gallery" class="content-section">
|
<div v-if="project.gallery" class="content-section">
|
||||||
<h2 class="section-title">Galerie</h2>
|
<h2 class="section-title">{{ t('projects.projectDetail.gallery') }}</h2>
|
||||||
<div class="section-content">
|
<div class="section-content">
|
||||||
<div class="gallery-grid">
|
<div class="gallery-grid">
|
||||||
<div v-for="(image, index) in project.gallery" :key="index" class="gallery-item">
|
<div v-for="(image, index) in project.gallery" :key="index" class="gallery-item"
|
||||||
|
@click="gallery.openGallery(project.gallery, index)">
|
||||||
<img :src="getImageUrl(image)" :alt="`${project.title} - Image ${index + 1}`" class="gallery-image">
|
<img :src="getImageUrl(image)" :alt="`${project.title} - Image ${index + 1}`" class="gallery-image">
|
||||||
|
<div class="gallery-overlay">
|
||||||
|
<svg class="gallery-expand-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -194,36 +211,31 @@ onMounted(() => {
|
|||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<!-- Project Info Card -->
|
<!-- Project Info Card -->
|
||||||
<div class="info-card">
|
<div class="info-card">
|
||||||
<h3 class="info-title">Informations du projet</h3>
|
<h3 class="info-title">{{ t('projects.projectDetail.projectInfo') }}</h3>
|
||||||
<div class="info-list">
|
<div class="info-list">
|
||||||
<div v-if="project.date" class="info-item">
|
<div v-if="project.date" class="info-item">
|
||||||
<span class="info-label">Date</span>
|
<span class="info-label">{{ t('projects.projectDetail.date') }}</span>
|
||||||
<span class="info-value">{{ project.date }}</span>
|
<span class="info-value">{{ project.date }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="project.category" class="info-item">
|
<div v-if="project.category" class="info-item">
|
||||||
<span class="info-label">Catégorie</span>
|
<span class="info-label">{{ t('projects.projectDetail.category') }}</span>
|
||||||
<span class="info-value">{{ project.category }}</span>
|
<span class="info-value">{{ project.category }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="project.status" class="info-item">
|
<div v-if="project.status" class="info-item">
|
||||||
<span class="info-label">Statut</span>
|
<span class="info-label">{{ t('projects.projectDetail.status') }}</span>
|
||||||
<span class="info-value">{{ project.status }}</span>
|
<span class="info-value">{{ project.status }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div v-if="project.duration" class="info-item">
|
|
||||||
<span class="info-label">Durée</span>
|
|
||||||
<span class="info-value">{{ project.duration }}</span>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Related Projects -->
|
<!-- Related Projects -->
|
||||||
<div v-if="relatedProjects.length > 0" class="related-projects">
|
<div v-if="relatedProjects.length > 0" class="related-projects">
|
||||||
<h3 class="related-title">Projets similaires</h3>
|
<h3 class="related-title">{{ t('projects.projectDetail.relatedProjects') }}</h3>
|
||||||
<div class="related-list">
|
<div class="related-list">
|
||||||
<router-link v-for="relatedProject in relatedProjects" :key="relatedProject.id"
|
<router-link v-for="relatedProject in relatedProjects" :key="relatedProject.id"
|
||||||
:to="`/projects/${relatedProject.id}`" class="related-item">
|
:to="`/project/${relatedProject.id}`" class="related-item">
|
||||||
<img v-if="relatedProject.image" :src="getImageUrl(relatedProject.image)" :alt="relatedProject.title"
|
<img v-if="relatedProject.image" :src="getImageUrl(relatedProject.image)" :alt="relatedProject.title"
|
||||||
class="related-image">
|
class="related-image">
|
||||||
<div class="related-content">
|
<div class="related-content">
|
||||||
@@ -237,6 +249,13 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Gallery Modal -->
|
||||||
|
<GalleryModal :is-open="gallery.isOpen.value" :current-image="gallery.currentImage.value"
|
||||||
|
:current-index="gallery.currentIndex.value" :total-images="gallery.images.value.length"
|
||||||
|
:has-next="gallery.hasNext.value" :has-previous="gallery.hasPrevious.value" :project-title="project?.title || ''"
|
||||||
|
@close="gallery.closeGallery" @next="gallery.nextImage" @previous="gallery.previousImage"
|
||||||
|
@go-to="gallery.goToImage" />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -2,10 +2,12 @@
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useSeo } from '@/composables/useSeo'
|
import { useSeo } from '@/composables/useSeo'
|
||||||
import { useI18n } from '@/composables/useI18n'
|
import { useI18n } from '@/composables/useI18n'
|
||||||
import { projects } from '@/data/projects'
|
import { useProjects } from '@/composables/useProjects'
|
||||||
import ProjectCard from '@/components/ProjectCard.vue'
|
import ProjectCard from '@/components/ProjectCard.vue'
|
||||||
|
import CTAButtons from '@/components/shared/CTAButtons.vue'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const { projects } = useProjects()
|
||||||
|
|
||||||
// SEO
|
// SEO
|
||||||
useSeo({
|
useSeo({
|
||||||
@@ -19,7 +21,7 @@ useSeo({
|
|||||||
'name': 'Web Development Portfolio Projects',
|
'name': 'Web Development Portfolio Projects',
|
||||||
'description': 'Browse professional web development projects including Vue.js applications, React websites, Node.js APIs, and Discord bots',
|
'description': 'Browse professional web development projects including Vue.js applications, React websites, Node.js APIs, and Discord bots',
|
||||||
'url': 'https://killiandalcin.fr/projects',
|
'url': 'https://killiandalcin.fr/projects',
|
||||||
'hasPart': projects.map(project => ({
|
'hasPart': projects.value.map(project => ({
|
||||||
'@type': 'CreativeWork',
|
'@type': 'CreativeWork',
|
||||||
'name': project.title,
|
'name': project.title,
|
||||||
'description': project.description,
|
'description': project.description,
|
||||||
@@ -36,13 +38,13 @@ const sortBy = ref('date')
|
|||||||
|
|
||||||
// Get unique categories
|
// Get unique categories
|
||||||
const categories = computed(() => {
|
const categories = computed(() => {
|
||||||
const cats = ['all', ...new Set(projects.map(p => p.category).filter(Boolean))]
|
const cats = ['all', ...new Set(projects.value.map(p => p.category).filter(Boolean))]
|
||||||
return cats
|
return cats
|
||||||
})
|
})
|
||||||
|
|
||||||
// Filtered and sorted projects
|
// Filtered and sorted projects
|
||||||
const filteredProjects = computed(() => {
|
const filteredProjects = computed(() => {
|
||||||
let filtered = projects
|
let filtered = projects.value
|
||||||
|
|
||||||
// Filter by search query
|
// Filter by search query
|
||||||
if (searchQuery.value) {
|
if (searchQuery.value) {
|
||||||
@@ -74,16 +76,16 @@ const filteredProjects = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
const totalProjects = computed(() => projects.length)
|
const totalProjects = computed(() => projects.value.length)
|
||||||
const featuredProjects = computed(() => projects.filter(p => p.featured).length)
|
const featuredProjects = computed(() => projects.value.filter(p => p.featured).length)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="projects-page">
|
<main class="projects-page page-enter">
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="projects-hero">
|
<section class="projects-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-content text-center">
|
<div class="hero-content text-center animate-fade-in-up">
|
||||||
<h1 class="hero-title">{{ t('projects.title') }}</h1>
|
<h1 class="hero-title">{{ t('projects.title') }}</h1>
|
||||||
<p class="hero-subtitle">
|
<p class="hero-subtitle">
|
||||||
{{ t('projects.subtitle') }}
|
{{ t('projects.subtitle') }}
|
||||||
@@ -111,7 +113,7 @@ const featuredProjects = computed(() => projects.filter(p => p.featured).length)
|
|||||||
<!-- Filters Section -->
|
<!-- Filters Section -->
|
||||||
<section class="filters-section">
|
<section class="filters-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="filters-container">
|
<div class="filters-container animate-fade-in-up" style="animation-delay: 0.2s;">
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<div class="search-input-wrapper">
|
<div class="search-input-wrapper">
|
||||||
@@ -150,7 +152,7 @@ const featuredProjects = computed(() => projects.filter(p => p.featured).length)
|
|||||||
<section class="projects-grid-section">
|
<section class="projects-grid-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- Results Info -->
|
<!-- Results Info -->
|
||||||
<div class="results-info">
|
<div class="results-info animate-fade-in-up" style="animation-delay: 0.3s;">
|
||||||
<p class="results-text">
|
<p class="results-text">
|
||||||
{{ filteredProjects.length }} {{ t('nav.projects').toLowerCase() }}{{ filteredProjects.length > 1 ? 's' : ''
|
{{ filteredProjects.length }} {{ t('nav.projects').toLowerCase() }}{{ filteredProjects.length > 1 ? 's' : ''
|
||||||
}} {{ t('common.search').toLowerCase() }}{{ filteredProjects.length > 1 ? 's' : '' }}
|
}} {{ t('common.search').toLowerCase() }}{{ filteredProjects.length > 1 ? 's' : '' }}
|
||||||
@@ -175,9 +177,11 @@ const featuredProjects = computed(() => projects.filter(p => p.featured).length)
|
|||||||
<p class="no-results-description">
|
<p class="no-results-description">
|
||||||
{{ t('projects.noResults.description') }}
|
{{ t('projects.noResults.description') }}
|
||||||
</p>
|
</p>
|
||||||
<button @click="searchQuery = ''; selectedCategory = 'all'" class="btn btn-primary">
|
<CTAButtons layout="stack">
|
||||||
{{ t('common.reset') }}
|
<button @click="searchQuery = ''; selectedCategory = 'all'" class="btn btn-primary">
|
||||||
</button>
|
{{ t('common.reset') }}
|
||||||
|
</button>
|
||||||
|
</CTAButtons>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
435
src/views/styles/FormationPage.css
Normal file
435
src/views/styles/FormationPage.css
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
.formation-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
|
||||||
|
padding-top: 80px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero Section */
|
||||||
|
.hero-section {
|
||||||
|
padding: var(--space-4xl) 0 var(--space-2xl);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: var(--font-size-5xl);
|
||||||
|
font-weight: var(--font-weight-extrabold);
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subtitle {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: var(--space-3xl);
|
||||||
|
max-width: 600px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
line-height: var(--line-height-relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Billing Toggle */
|
||||||
|
.billing-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-md);
|
||||||
|
margin-bottom: var(--space-2xl);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
padding: var(--space-md) var(--space-2xl);
|
||||||
|
border-radius: var(--border-radius-full);
|
||||||
|
border: var(--border-width) solid var(--border-color);
|
||||||
|
display: inline-flex;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.billing-toggle span {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
transition: var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.billing-toggle span.active {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.discount-badge {
|
||||||
|
background: var(--color-success);
|
||||||
|
color: var(--text-inverse);
|
||||||
|
padding: var(--space-xs) var(--space-sm);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
margin-left: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle Switch */
|
||||||
|
.toggle-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: var(--color-gray-300);
|
||||||
|
transition: var(--transition-normal);
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .slider {
|
||||||
|
background-color: var(--color-gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
transition: var(--transition-normal);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pricing Section */
|
||||||
|
.pricing-section {
|
||||||
|
padding: var(--space-2xl) 0 var(--space-4xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: var(--space-xl);
|
||||||
|
max-width: 1600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--space-md);
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pricing Cards */
|
||||||
|
.pricing-card {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: var(--border-radius-2xl);
|
||||||
|
padding: var(--space-2xl);
|
||||||
|
position: relative;
|
||||||
|
box-shadow: var(--shadow-xl);
|
||||||
|
transition: var(--transition-normal);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card:hover {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
box-shadow: var(--shadow-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card.popular {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 25px 50px rgba(133, 203, 133, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .pricing-card.popular {
|
||||||
|
box-shadow: 0 25px 50px rgba(163, 214, 163, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card.popular:hover {
|
||||||
|
transform: scale(1.05) translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popular-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -15px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
|
||||||
|
color: var(--text-inverse);
|
||||||
|
padding: var(--space-sm) var(--space-lg);
|
||||||
|
border-radius: 25px;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
box-shadow: 0 4px 12px rgba(133, 203, 133, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .popular-badge {
|
||||||
|
box-shadow: 0 4px 12px rgba(163, 214, 163, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Header */
|
||||||
|
.card-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: var(--space-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-name {
|
||||||
|
font-size: var(--font-size-2xl);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-price {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
font-size: var(--font-size-5xl);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0 var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.period {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-description {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: var(--line-height-relaxed);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Body */
|
||||||
|
.card-body {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Features List */
|
||||||
|
.features-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 var(--space-lg) 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--space-md) 0;
|
||||||
|
border-bottom: var(--border-width) solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: var(--color-success);
|
||||||
|
margin-right: var(--space-md);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Actions */
|
||||||
|
.card-actions {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CTA Button */
|
||||||
|
.cta-button {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--space-md) var(--space-2xl);
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-normal);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button.primary {
|
||||||
|
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
|
||||||
|
color: var(--text-inverse);
|
||||||
|
box-shadow: 0 4px 12px rgba(133, 203, 133, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .cta-button.primary {
|
||||||
|
box-shadow: 0 4px 12px rgba(163, 214, 163, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button.primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(133, 203, 133, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .cta-button.primary:hover {
|
||||||
|
box-shadow: 0 6px 20px rgba(163, 214, 163, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button.secondary {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-button.secondary:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trial-info {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FAQ Section */
|
||||||
|
.faq-section {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: var(--space-4xl) 0;
|
||||||
|
border-top: var(--border-width) solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--font-size-4xl);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--space-3xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||||
|
gap: var(--space-2xl);
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
padding: var(--space-2xl);
|
||||||
|
border-radius: var(--border-radius-xl);
|
||||||
|
border: var(--border-width) solid var(--border-color);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transition: var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item:hover {
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-question {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-answer {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: var(--line-height-relaxed);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.container {
|
||||||
|
max-width: 1600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.pricing-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--space-xl);
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card.popular {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card.popular:hover {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero-title {
|
||||||
|
font-size: var(--font-size-4xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.billing-toggle {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.hero-title {
|
||||||
|
font-size: var(--font-size-3xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
font-size: var(--font-size-4xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card {
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item {
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
}
|
@@ -6,6 +6,43 @@
|
|||||||
padding: var(--space-4xl) 0;
|
padding: var(--space-4xl) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Services Grid - 2x2 Layout */
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Projects Grid - 3 Columns */
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Service Icons */
|
/* Service Icons */
|
||||||
.service-icon {
|
.service-icon {
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
|
@@ -273,6 +273,8 @@
|
|||||||
box-shadow: var(--shadow-lg);
|
box-shadow: var(--shadow-lg);
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item:hover {
|
.gallery-item:hover {
|
||||||
@@ -280,10 +282,41 @@
|
|||||||
box-shadow: var(--shadow-xl);
|
box-shadow: var(--shadow-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gallery-item:hover .gallery-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.gallery-image {
|
.gallery-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
transition: transform var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item:hover .gallery-image {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity var(--transition-normal);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-expand-icon {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
color: white;
|
||||||
|
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
|
Reference in New Issue
Block a user