fix: update portfolio branding to "Killian' DAL-CIN" across all documentation and components
- Corrected the name in various files including CLAUDE.md, README.md, and configuration files to reflect the updated branding. - Ensured consistency in the use of the new name throughout the project, enhancing brand identity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
# Portfolio Killian Dalcin — Migration Nuxt 4
|
# Portfolio Killian' DAL-CIN — Migration Nuxt 4
|
||||||
|
|
||||||
## What This Is
|
## What This Is
|
||||||
|
|
||||||
Migration complète d'un portfolio freelance de Vue 3 SPA vers Nuxt 4 avec SSR complet. Le site présente les projets, services et compétences de Killian Dalcin, développeur freelance, avec support bilingue FR/EN. L'objectif est un SEO parfait et un développement rapide via des composants prêts à l'emploi (Nuxt UI v3).
|
Migration complète d'un portfolio freelance de Vue 3 SPA vers Nuxt 4 avec SSR complet. Le site présente les projets, services et compétences de Killian' DAL-CIN, développeur freelance, avec support bilingue FR/EN. L'objectif est un SEO parfait et un développement rapide via des composants prêts à l'emploi (Nuxt UI v3).
|
||||||
|
|
||||||
## Core Value
|
## Core Value
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Requirements: Portfolio Killian Dalcin — Nuxt 4 Migration
|
# Requirements: Portfolio Killian' DAL-CIN — Nuxt 4 Migration
|
||||||
|
|
||||||
**Defined:** 2026-04-07
|
**Defined:** 2026-04-07
|
||||||
**Core Value:** Chaque page du portfolio doit être crawlable par les moteurs de recherche sans JavaScript côté client
|
**Core Value:** Chaque page du portfolio doit être crawlable par les moteurs de recherche sans JavaScript côté client
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Roadmap: Portfolio Killian Dalcin — Nuxt 4 Migration
|
# Roadmap: Portfolio Killian' DAL-CIN — Nuxt 4 Migration
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ export interface TechStack {
|
|||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>Portfolio Killian Dalcin</h1>
|
<h1>Portfolio Killian' DAL-CIN</h1>
|
||||||
<p>Nuxt 4 Foundation</p>
|
<p>Nuxt 4 Foundation</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export default defineAppConfig({
|
|||||||
- `css: ['~/assets/css/main.css']`
|
- `css: ['~/assets/css/main.css']`
|
||||||
- `colorMode` block: `{ preference: 'dark', fallback: 'dark', storage: 'cookie', storageKey: 'nuxt-color-mode', cookieName: 'nuxt-color-mode', classSuffix: '' }`
|
- `colorMode` block: `{ preference: 'dark', fallback: 'dark', storage: 'cookie', storageKey: 'nuxt-color-mode', cookieName: 'nuxt-color-mode', classSuffix: '' }`
|
||||||
- `i18n.baseUrl: 'https://killiandalcin.fr'`
|
- `i18n.baseUrl: 'https://killiandalcin.fr'`
|
||||||
- `site: { url: 'https://killiandalcin.fr', name: 'Killian Dalcin - Developpeur Full Stack' }`
|
- `site: { url: 'https://killiandalcin.fr', name: 'Killian' DAL-CIN - Developpeur Full Stack' }`
|
||||||
|
|
||||||
Do NOT touch existing modules array or i18n locale config — they are correct from Phase 1.
|
Do NOT touch existing modules array or i18n locale config — they are correct from Phase 1.
|
||||||
|
|
||||||
@@ -171,44 +171,44 @@ fr.json top-level structure:
|
|||||||
"formation": "Formation"
|
"formation": "Formation"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"copyright": "© 2026 Killian Dalcin"
|
"copyright": "© 2026 Killian' DAL-CIN"
|
||||||
},
|
},
|
||||||
"a11y": {
|
"a11y": {
|
||||||
"logoLabel": "Killian Dalcin — Developpeur Full Stack — Retour a l'accueil",
|
"logoLabel": "Killian' DAL-CIN — Developpeur Full Stack — Retour a l'accueil",
|
||||||
"openMenu": "Ouvrir le menu de navigation",
|
"openMenu": "Ouvrir le menu de navigation",
|
||||||
"closeMenu": "Fermer le menu de navigation",
|
"closeMenu": "Fermer le menu de navigation",
|
||||||
"closeDrawer": "Fermer le menu",
|
"closeDrawer": "Fermer le menu",
|
||||||
"langToggle": "Changer la langue — actuellement Francais",
|
"langToggle": "Changer la langue — actuellement Francais",
|
||||||
"themeDark": "Activer le mode clair",
|
"themeDark": "Activer le mode clair",
|
||||||
"themeLight": "Activer le mode sombre",
|
"themeLight": "Activer le mode sombre",
|
||||||
"github": "GitHub de Killian Dalcin (nouvelle fenetre)",
|
"github": "GitHub de Killian' DAL-CIN (nouvelle fenetre)",
|
||||||
"linkedin": "LinkedIn de Killian Dalcin (nouvelle fenetre)",
|
"linkedin": "LinkedIn de Killian' DAL-CIN (nouvelle fenetre)",
|
||||||
"fiverr": "Fiverr de Killian Dalcin (nouvelle fenetre)"
|
"fiverr": "Fiverr de Killian' DAL-CIN (nouvelle fenetre)"
|
||||||
},
|
},
|
||||||
"seo": {
|
"seo": {
|
||||||
"home": {
|
"home": {
|
||||||
"title": "Killian Dalcin — Developpeur Full Stack Freelance",
|
"title": "Killian' DAL-CIN — Developpeur Full Stack Freelance",
|
||||||
"description": "Portfolio de Killian Dalcin, developpeur full stack freelance specialise en Vue.js, React et Node.js. Applications web performantes et solutions sur-mesure."
|
"description": "Portfolio de Killian' DAL-CIN, developpeur full stack freelance specialise en Vue.js, React et Node.js. Applications web performantes et solutions sur-mesure."
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"title": "Projets — Killian Dalcin",
|
"title": "Projets — Killian' DAL-CIN",
|
||||||
"description": "Decouvrez mes realisations en developpement web : applications Vue.js, API Node.js, bots Discord et solutions d'entreprise."
|
"description": "Decouvrez mes realisations en developpement web : applications Vue.js, API Node.js, bots Discord et solutions d'entreprise."
|
||||||
},
|
},
|
||||||
"about": {
|
"about": {
|
||||||
"title": "A propos — Killian Dalcin",
|
"title": "A propos — Killian' DAL-CIN",
|
||||||
"description": "Biographie et competences de Killian Dalcin, developpeur full stack freelance base en France."
|
"description": "Biographie et competences de Killian' DAL-CIN, developpeur full stack freelance base en France."
|
||||||
},
|
},
|
||||||
"contact": {
|
"contact": {
|
||||||
"title": "Contact — Killian Dalcin",
|
"title": "Contact — Killian' DAL-CIN",
|
||||||
"description": "Contactez Killian Dalcin pour discuter de votre projet de developpement web."
|
"description": "Contactez Killian' DAL-CIN pour discuter de votre projet de developpement web."
|
||||||
},
|
},
|
||||||
"fiverr": {
|
"fiverr": {
|
||||||
"title": "Services Fiverr — Killian Dalcin",
|
"title": "Services Fiverr — Killian' DAL-CIN",
|
||||||
"description": "Services de developpement disponibles sur Fiverr : bots Discord, plugins Minecraft, applications web."
|
"description": "Services de developpement disponibles sur Fiverr : bots Discord, plugins Minecraft, applications web."
|
||||||
},
|
},
|
||||||
"formation": {
|
"formation": {
|
||||||
"title": "Formation — Killian Dalcin",
|
"title": "Formation — Killian' DAL-CIN",
|
||||||
"description": "Formations et cours proposes par Killian Dalcin en developpement web."
|
"description": "Formations et cours proposes par Killian' DAL-CIN en developpement web."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,44 +226,44 @@ en.json same structure with English translations:
|
|||||||
"formation": "Training"
|
"formation": "Training"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"copyright": "© 2026 Killian Dalcin"
|
"copyright": "© 2026 Killian' DAL-CIN"
|
||||||
},
|
},
|
||||||
"a11y": {
|
"a11y": {
|
||||||
"logoLabel": "Killian Dalcin — Full Stack Developer — Back to homepage",
|
"logoLabel": "Killian' DAL-CIN — Full Stack Developer — Back to homepage",
|
||||||
"openMenu": "Open navigation menu",
|
"openMenu": "Open navigation menu",
|
||||||
"closeMenu": "Close navigation menu",
|
"closeMenu": "Close navigation menu",
|
||||||
"closeDrawer": "Close menu",
|
"closeDrawer": "Close menu",
|
||||||
"langToggle": "Change language — currently English",
|
"langToggle": "Change language — currently English",
|
||||||
"themeDark": "Switch to light mode",
|
"themeDark": "Switch to light mode",
|
||||||
"themeLight": "Switch to dark mode",
|
"themeLight": "Switch to dark mode",
|
||||||
"github": "Killian Dalcin on GitHub (opens in new tab)",
|
"github": "Killian' DAL-CIN on GitHub (opens in new tab)",
|
||||||
"linkedin": "Killian Dalcin on LinkedIn (opens in new tab)",
|
"linkedin": "Killian' DAL-CIN on LinkedIn (opens in new tab)",
|
||||||
"fiverr": "Killian Dalcin on Fiverr (opens in new tab)"
|
"fiverr": "Killian' DAL-CIN on Fiverr (opens in new tab)"
|
||||||
},
|
},
|
||||||
"seo": {
|
"seo": {
|
||||||
"home": {
|
"home": {
|
||||||
"title": "Killian Dalcin — Freelance Full Stack Developer",
|
"title": "Killian' DAL-CIN — Freelance Full Stack Developer",
|
||||||
"description": "Portfolio of Killian Dalcin, freelance full stack developer specializing in Vue.js, React and Node.js. High-performance web applications and custom solutions."
|
"description": "Portfolio of Killian' DAL-CIN, freelance full stack developer specializing in Vue.js, React and Node.js. High-performance web applications and custom solutions."
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"title": "Projects — Killian Dalcin",
|
"title": "Projects — Killian' DAL-CIN",
|
||||||
"description": "Discover my web development projects: Vue.js applications, Node.js APIs, Discord bots and enterprise solutions."
|
"description": "Discover my web development projects: Vue.js applications, Node.js APIs, Discord bots and enterprise solutions."
|
||||||
},
|
},
|
||||||
"about": {
|
"about": {
|
||||||
"title": "About — Killian Dalcin",
|
"title": "About — Killian' DAL-CIN",
|
||||||
"description": "Biography and skills of Killian Dalcin, freelance full stack developer based in France."
|
"description": "Biography and skills of Killian' DAL-CIN, freelance full stack developer based in France."
|
||||||
},
|
},
|
||||||
"contact": {
|
"contact": {
|
||||||
"title": "Contact — Killian Dalcin",
|
"title": "Contact — Killian' DAL-CIN",
|
||||||
"description": "Contact Killian Dalcin to discuss your web development project."
|
"description": "Contact Killian' DAL-CIN to discuss your web development project."
|
||||||
},
|
},
|
||||||
"fiverr": {
|
"fiverr": {
|
||||||
"title": "Fiverr Services — Killian Dalcin",
|
"title": "Fiverr Services — Killian' DAL-CIN",
|
||||||
"description": "Development services available on Fiverr: Discord bots, Minecraft plugins, web applications."
|
"description": "Development services available on Fiverr: Discord bots, Minecraft plugins, web applications."
|
||||||
},
|
},
|
||||||
"formation": {
|
"formation": {
|
||||||
"title": "Training — Killian Dalcin",
|
"title": "Training — Killian' DAL-CIN",
|
||||||
"description": "Training and courses offered by Killian Dalcin in web development."
|
"description": "Training and courses offered by Killian' DAL-CIN in web development."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ Create `app/components/layout/AppHeader.vue` as a single-file component containi
|
|||||||
|
|
||||||
**Left — Logo:**
|
**Left — Logo:**
|
||||||
- `<NuxtLink to="localePath('/')" :aria-label="t('a11y.logoLabel')">`
|
- `<NuxtLink to="localePath('/')" :aria-label="t('a11y.logoLabel')">`
|
||||||
- Contains `<NuxtImg src="/images/logo.webp" alt="Killian Dalcin" width="40" height="40" loading="eager" />` + `<span class="text-lg font-semibold">Killian</span>`
|
- Contains `<NuxtImg src="/images/logo.webp" alt="Killian' DAL-CIN" width="40" height="40" loading="eager" />` + `<span class="text-lg font-semibold">Killian</span>`
|
||||||
|
|
||||||
**Center-Right — Desktop nav (hidden md:flex):**
|
**Center-Right — Desktop nav (hidden md:flex):**
|
||||||
- Use `<nav>` with `<NuxtLink>` for each route: home(/), projects(/projects), about(/about), contact(/contact), fiverr(/fiverr), formation(/formation)
|
- Use `<nav>` with `<NuxtLink>` for each route: home(/), projects(/projects), about(/about), contact(/contact), fiverr(/fiverr), formation(/formation)
|
||||||
@@ -226,7 +226,7 @@ Template:
|
|||||||
- Icon: `<UIcon :name="link.icon" class="w-5 h-5 text-gray-500 dark:text-gray-400 hover:text-primary-500 transition-colors duration-150" />`
|
- Icon: `<UIcon :name="link.icon" class="w-5 h-5 text-gray-500 dark:text-gray-400 hover:text-primary-500 transition-colors duration-150" />`
|
||||||
- Focus ring on each `<a>`: `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2`
|
- Focus ring on each `<a>`: `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2`
|
||||||
|
|
||||||
Note: a11y.github key text says "GitHub de Killian Dalcin" but links to Gitea — the executor should update the a11y key in fr.json/en.json to say "Gitea" instead of "GitHub" if not already correct. Check and fix if needed.
|
Note: a11y.github key text says "GitHub de Killian' DAL-CIN" but links to Gitea — the executor should update the a11y key in fr.json/en.json to say "Gitea" instead of "GitHub" if not already correct. Check and fix if needed.
|
||||||
|
|
||||||
2. Create `app/layouts/default.vue` (per D-15):
|
2. Create `app/layouts/default.vue` (per D-15):
|
||||||
```vue
|
```vue
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ useHead({
|
|||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'Person',
|
'@type': 'Person',
|
||||||
name: 'Killian Dalcin',
|
name: 'Killian' DAL-CIN',
|
||||||
url: 'https://killiandalcin.fr',
|
url: 'https://killiandalcin.fr',
|
||||||
jobTitle: 'Developpeur Full Stack Freelance',
|
jobTitle: 'Developpeur Full Stack Freelance',
|
||||||
email: 'contact@killiandalcin.fr',
|
email: 'contact@killiandalcin.fr',
|
||||||
@@ -140,7 +140,7 @@ useHead({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'ProfessionalService',
|
'@type': 'ProfessionalService',
|
||||||
name: 'Killian Dalcin - Developpeur Full Stack',
|
name: 'Killian' DAL-CIN - Developpeur Full Stack',
|
||||||
url: 'https://killiandalcin.fr',
|
url: 'https://killiandalcin.fr',
|
||||||
logo: 'https://killiandalcin.fr/images/logo.webp',
|
logo: 'https://killiandalcin.fr/images/logo.webp',
|
||||||
priceRange: '$$$',
|
priceRange: '$$$',
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Chaque route rend la bonne langue (FR/EN), le bon thème (dark/light), et les bo
|
|||||||
- **D-04:** Switch langue = bouton texte simple FR/EN (toggle au clic), pas de dropdown ni drapeaux
|
- **D-04:** Switch langue = bouton texte simple FR/EN (toggle au clic), pas de dropdown ni drapeaux
|
||||||
|
|
||||||
### Footer
|
### Footer
|
||||||
- **D-05:** Footer minimaliste — une seule bande : copyright © 2026 Killian Dalcin + icônes réseaux sociaux (Gitea, LinkedIn, Fiverr). Note : siteConfig pointe vers gitea.kamisama.ovh, pas GitHub.
|
- **D-05:** Footer minimaliste — une seule bande : copyright © 2026 Killian' DAL-CIN + icônes réseaux sociaux (Gitea, LinkedIn, Fiverr). Note : siteConfig pointe vers gitea.kamisama.ovh, pas GitHub.
|
||||||
|
|
||||||
### i18n SSR
|
### i18n SSR
|
||||||
- **D-06:** Enrichir les fichiers existants fr.json/en.json avec les clés navigation, footer et SEO — un seul fichier par langue
|
- **D-06:** Enrichir les fichiers existants fr.json/en.json avec les clés navigation, footer et SEO — un seul fichier par langue
|
||||||
@@ -32,7 +32,7 @@ Chaque route rend la bonne langue (FR/EN), le bon thème (dark/light), et les bo
|
|||||||
|
|
||||||
### SEO & Métadonnées
|
### SEO & Métadonnées
|
||||||
- **D-10:** useSeoMeta() par route — title, description, og:title, og:description uniques
|
- **D-10:** useSeoMeta() par route — title, description, og:title, og:description uniques
|
||||||
- **D-11:** JSON-LD sur la page d'accueil : schéma Person + ProfessionalService pour Killian Dalcin
|
- **D-11:** JSON-LD sur la page d'accueil : schéma Person + ProfessionalService pour Killian' DAL-CIN
|
||||||
- **D-12:** og:image statique dans public/ (og-image.png 1200x630) — nuxt-og-image dynamique reporté à Phase 3 suite aux risques Windows identifiés en recherche
|
- **D-12:** og:image statique dans public/ (og-image.png 1200x630) — nuxt-og-image dynamique reporté à Phase 3 suite aux risques Windows identifiés en recherche
|
||||||
|
|
||||||
### Sitemap
|
### Sitemap
|
||||||
|
|||||||
@@ -15,13 +15,13 @@
|
|||||||
- **D-02:** Mobile nav via UDrawer (Nuxt UI v3) — hamburger opens left-sliding drawer
|
- **D-02:** Mobile nav via UDrawer (Nuxt UI v3) — hamburger opens left-sliding drawer
|
||||||
- **D-03:** Header sticky permanent
|
- **D-03:** Header sticky permanent
|
||||||
- **D-04:** Language switch = simple text button FR/EN (toggle on click), no dropdown, no flags
|
- **D-04:** Language switch = simple text button FR/EN (toggle on click), no dropdown, no flags
|
||||||
- **D-05:** Footer minimal — single band: copyright © 2026 Killian Dalcin + social icons (GitHub, LinkedIn, Fiverr)
|
- **D-05:** Footer minimal — single band: copyright © 2026 Killian' DAL-CIN + social icons (GitHub, LinkedIn, Fiverr)
|
||||||
- **D-06:** Enrich existing fr.json/en.json with nav, footer, SEO keys — one file per language
|
- **D-06:** Enrich existing fr.json/en.json with nav, footer, SEO keys — one file per language
|
||||||
- **D-07:** @nuxtjs/i18n already configured: strategy prefix_except_default, FR default, browser detection + cookie
|
- **D-07:** @nuxtjs/i18n already configured: strategy prefix_except_default, FR default, browser detection + cookie
|
||||||
- **D-08:** Dark mode default for new visitors
|
- **D-08:** Dark mode default for new visitors
|
||||||
- **D-09:** Cookie persistence via @nuxtjs/color-mode — no localStorage, no FOUC
|
- **D-09:** Cookie persistence via @nuxtjs/color-mode — no localStorage, no FOUC
|
||||||
- **D-10:** useSeoMeta() per route — unique title, description, og:title, og:description
|
- **D-10:** useSeoMeta() per route — unique title, description, og:title, og:description
|
||||||
- **D-11:** JSON-LD on homepage: Person + ProfessionalService schema for Killian Dalcin
|
- **D-11:** JSON-LD on homepage: Person + ProfessionalService schema for Killian' DAL-CIN
|
||||||
- **D-12:** og:image dynamic via nuxt-og-image (advanced from SEOV2-01)
|
- **D-12:** og:image dynamic via nuxt-og-image (advanced from SEOV2-01)
|
||||||
- **D-13:** All public pages in sitemap except 404
|
- **D-13:** All public pages in sitemap except 404
|
||||||
- **D-14:** hreflang FR/EN alternates automatic via @nuxtjs/sitemap + @nuxtjs/i18n integration
|
- **D-14:** hreflang FR/EN alternates automatic via @nuxtjs/sitemap + @nuxtjs/i18n integration
|
||||||
@@ -240,7 +240,7 @@ useHead({
|
|||||||
innerHTML: JSON.stringify({
|
innerHTML: JSON.stringify({
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'Person',
|
'@type': 'Person',
|
||||||
name: 'Killian Dalcin',
|
name: 'Killian' DAL-CIN',
|
||||||
url: 'https://killiandalcin.fr',
|
url: 'https://killiandalcin.fr',
|
||||||
jobTitle: 'Développeur Full Stack Freelance',
|
jobTitle: 'Développeur Full Stack Freelance',
|
||||||
sameAs: [
|
sameAs: [
|
||||||
@@ -326,7 +326,7 @@ useSeoMeta({
|
|||||||
// nuxt.config.ts
|
// nuxt.config.ts
|
||||||
site: {
|
site: {
|
||||||
url: 'https://killiandalcin.fr',
|
url: 'https://killiandalcin.fr',
|
||||||
name: 'Killian Dalcin — Développeur Full Stack',
|
name: 'Killian' DAL-CIN — Développeur Full Stack',
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -423,7 +423,7 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
site: {
|
site: {
|
||||||
url: 'https://killiandalcin.fr',
|
url: 'https://killiandalcin.fr',
|
||||||
name: 'Killian Dalcin — Développeur Full Stack',
|
name: 'Killian' DAL-CIN — Développeur Full Stack',
|
||||||
},
|
},
|
||||||
|
|
||||||
colorMode: {
|
colorMode: {
|
||||||
@@ -549,7 +549,7 @@ useHead({
|
|||||||
{
|
{
|
||||||
'@type': 'Person',
|
'@type': 'Person',
|
||||||
'@id': 'https://killiandalcin.fr/#person',
|
'@id': 'https://killiandalcin.fr/#person',
|
||||||
name: 'Killian Dalcin',
|
name: 'Killian' DAL-CIN',
|
||||||
url: 'https://killiandalcin.fr',
|
url: 'https://killiandalcin.fr',
|
||||||
jobTitle: 'Développeur Full Stack Freelance',
|
jobTitle: 'Développeur Full Stack Freelance',
|
||||||
email: 'contact@killiandalcin.fr',
|
email: 'contact@killiandalcin.fr',
|
||||||
@@ -561,7 +561,7 @@ useHead({
|
|||||||
{
|
{
|
||||||
'@type': 'ProfessionalService',
|
'@type': 'ProfessionalService',
|
||||||
'@id': 'https://killiandalcin.fr/#service',
|
'@id': 'https://killiandalcin.fr/#service',
|
||||||
name: 'Killian Dalcin — Développeur Full Stack',
|
name: 'Killian' DAL-CIN — Développeur Full Stack',
|
||||||
url: 'https://killiandalcin.fr',
|
url: 'https://killiandalcin.fr',
|
||||||
provider: { '@id': 'https://killiandalcin.fr/#person' },
|
provider: { '@id': 'https://killiandalcin.fr/#person' },
|
||||||
priceRange: '€€€',
|
priceRange: '€€€',
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ Components delivered in this phase only:
|
|||||||
### AppFooter (COMP-06)
|
### AppFooter (COMP-06)
|
||||||
- Single band: `py-6 bg-gray-100 dark:bg-gray-800`
|
- Single band: `py-6 bg-gray-100 dark:bg-gray-800`
|
||||||
- Layout: flex row on md+, flex column on mobile — `items-center justify-between gap-4`
|
- Layout: flex row on md+, flex column on mobile — `items-center justify-between gap-4`
|
||||||
- Left: copyright text — "© 2026 Killian Dalcin"
|
- Left: copyright text — "© 2026 Killian' DAL-CIN"
|
||||||
- Right: social icon links — GitHub (`simple-icons:github`), LinkedIn (`simple-icons:linkedin`), Fiverr (`simple-icons:fiverr`)
|
- Right: social icon links — GitHub (`simple-icons:github`), LinkedIn (`simple-icons:linkedin`), Fiverr (`simple-icons:fiverr`)
|
||||||
- Icon size: 20px (w-5 h-5). Hover: accent color with `transition-colors duration-150`
|
- Icon size: 20px (w-5 h-5). Hover: accent color with `transition-colors duration-150`
|
||||||
- All links open in `_blank` with `rel="noopener noreferrer"` and `aria-label`
|
- All links open in `_blank` with `rel="noopener noreferrer"` and `aria-label`
|
||||||
@@ -175,7 +175,7 @@ Phase 2 scope: header nav labels, footer copyright, mobile drawer, language/them
|
|||||||
|
|
||||||
| Element | Copy (FR) | Copy (EN) |
|
| Element | Copy (FR) | Copy (EN) |
|
||||||
|---------|-----------|-----------|
|
|---------|-----------|-----------|
|
||||||
| Logo aria-label | "Killian Dalcin — Développeur Full Stack — Retour à l'accueil" | "Killian Dalcin — Full Stack Developer — Back to homepage" |
|
| Logo aria-label | "Killian' DAL-CIN — Développeur Full Stack — Retour à l'accueil" | "Killian' DAL-CIN — Full Stack Developer — Back to homepage" |
|
||||||
| Nav: Home | "Accueil" | "Home" |
|
| Nav: Home | "Accueil" | "Home" |
|
||||||
| Nav: Projects | "Projets" | "Projects" |
|
| Nav: Projects | "Projets" | "Projects" |
|
||||||
| Nav: About | "À propos" | "About" |
|
| Nav: About | "À propos" | "About" |
|
||||||
@@ -188,10 +188,10 @@ Phase 2 scope: header nav labels, footer copyright, mobile drawer, language/them
|
|||||||
| Language toggle aria-label | "Changer la langue — actuellement Français" | "Change language — currently English" |
|
| Language toggle aria-label | "Changer la langue — actuellement Français" | "Change language — currently English" |
|
||||||
| Theme toggle aria-label (dark) | "Activer le mode clair" | "Switch to light mode" |
|
| Theme toggle aria-label (dark) | "Activer le mode clair" | "Switch to light mode" |
|
||||||
| Theme toggle aria-label (light) | "Activer le mode sombre" | "Switch to dark mode" |
|
| Theme toggle aria-label (light) | "Activer le mode sombre" | "Switch to dark mode" |
|
||||||
| Footer copyright | "© 2026 Killian Dalcin" | "© 2026 Killian Dalcin" |
|
| Footer copyright | "© 2026 Killian' DAL-CIN" | "© 2026 Killian' DAL-CIN" |
|
||||||
| GitHub icon aria-label | "GitHub de Killian Dalcin (nouvelle fenêtre)" | "Killian Dalcin on GitHub (opens in new tab)" |
|
| GitHub icon aria-label | "GitHub de Killian' DAL-CIN (nouvelle fenêtre)" | "Killian' DAL-CIN on GitHub (opens in new tab)" |
|
||||||
| LinkedIn icon aria-label | "LinkedIn de Killian Dalcin (nouvelle fenêtre)" | "Killian Dalcin on LinkedIn (opens in new tab)" |
|
| LinkedIn icon aria-label | "LinkedIn de Killian' DAL-CIN (nouvelle fenêtre)" | "Killian' DAL-CIN on LinkedIn (opens in new tab)" |
|
||||||
| Fiverr icon aria-label | "Fiverr de Killian Dalcin (nouvelle fenêtre)" | "Killian Dalcin on Fiverr (opens in new tab)" |
|
| Fiverr icon aria-label | "Fiverr de Killian' DAL-CIN (nouvelle fenêtre)" | "Killian' DAL-CIN on Fiverr (opens in new tab)" |
|
||||||
|
|
||||||
Destructive confirmation: none — Phase 2 has no destructive actions.
|
Destructive confirmation: none — Phase 2 has no destructive actions.
|
||||||
Empty state: none — Phase 2 has no data-driven content.
|
Empty state: none — Phase 2 has no data-driven content.
|
||||||
|
|||||||
@@ -640,7 +640,7 @@ if (!name || !email || !message || message.length > 5000) {
|
|||||||
|---|-------|---------|-------------|
|
|---|-------|---------|-------------|
|
||||||
| A1 | `@types/nodemailer` est le package de types correct pour nodemailer 8.x | Standard Stack | Types manquants — TypeScript strict échouera ; vérifier avec `npm view @types/nodemailer` |
|
| A1 | `@types/nodemailer` est le package de types correct pour nodemailer 8.x | Standard Stack | Types manquants — TypeScript strict échouera ; vérifier avec `npm view @types/nodemailer` |
|
||||||
| A2 | OVH SMTP fonctionne sur ssl0.ovh.net:465 avec auth PLAIN | Pattern 3 | L'envoi échoue — tester avec les vraies credentials avant de fermer la phase |
|
| A2 | OVH SMTP fonctionne sur ssl0.ovh.net:465 avec auth PLAIN | Pattern 3 | L'envoi échoue — tester avec les vraies credentials avant de fermer la phase |
|
||||||
| A3 | Docker est disponible sur la machine de déploiement de Killian | Environment Availability | INFRA-01 bloqué — confirmer avec `docker --version` |
|
| A3 | Docker est disponible sur la machine de déploiement de Killian'| Environment Availability | INFRA-01 bloqué — confirmer avec `docker --version` |
|
||||||
| A4 | `UCarousel` expose `emblaApi` directement via `useTemplateRef` dans Nuxt UI v3 | Pattern 1 | Navigation thumbnail cassée — fallback : gérer index manuellement avec `watch` |
|
| A4 | `UCarousel` expose `emblaApi` directement via `useTemplateRef` dans Nuxt UI v3 | Pattern 1 | Navigation thumbnail cassée — fallback : gérer index manuellement avec `watch` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Architecture Patterns
|
# Architecture Patterns
|
||||||
|
|
||||||
**Project:** Portfolio Killian Dalcin — Nuxt 4 SSR Migration
|
**Project:** Portfolio Killian' DAL-CIN — Nuxt 4 SSR Migration
|
||||||
**Researched:** 2026-04-07
|
**Researched:** 2026-04-07
|
||||||
**Confidence:** HIGH (based on official Nuxt 4 conventions + existing codebase analysis)
|
**Confidence:** HIGH (based on official Nuxt 4 conventions + existing codebase analysis)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Technology Stack
|
# Technology Stack
|
||||||
|
|
||||||
**Project:** Portfolio Killian Dalcin — Nuxt 4 SSR Migration
|
**Project:** Portfolio Killian' DAL-CIN — Nuxt 4 SSR Migration
|
||||||
**Researched:** 2026-04-07
|
**Researched:** 2026-04-07
|
||||||
**Knowledge cutoff:** August 2025 — all versions marked LOW confidence must be verified against npm before pinning
|
**Knowledge cutoff:** August 2025 — all versions marked LOW confidence must be verified against npm before pinning
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Research Summary
|
# Research Summary
|
||||||
|
|
||||||
**Project:** Portfolio Killian Dalcin — Vue 3 SPA → Nuxt 4 SSR Migration
|
**Project:** Portfolio Killian' DAL-CIN — Vue 3 SPA → Nuxt 4 SSR Migration
|
||||||
**Date:** 2026-04-07
|
**Date:** 2026-04-07
|
||||||
**Sources:** STACK.md, FEATURES.md, ARCHITECTURE.md, PITFALLS.md
|
**Sources:** STACK.md, FEATURES.md, ARCHITECTURE.md, PITFALLS.md
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- GSD:project-start source:PROJECT.md -->
|
<!-- GSD:project-start source:PROJECT.md -->
|
||||||
## Project
|
## Project
|
||||||
|
|
||||||
**Portfolio Killian Dalcin — Migration Nuxt 4**
|
**Portfolio Killian' Dalcin — Migration Nuxt 4**
|
||||||
|
|
||||||
Migration complète d'un portfolio freelance de Vue 3 SPA vers Nuxt 4 avec SSR complet. Le site présente les projets, services et compétences de Killian Dalcin, développeur freelance, avec support bilingue FR/EN. L'objectif est un SEO parfait et un développement rapide via des composants prêts à l'emploi (Nuxt UI v3).
|
Migration complète d'un portfolio freelance de Vue 3 SPA vers Nuxt 4 avec SSR complet. Le site présente les projets, services et compétences de Killian' Dalcin, développeur freelance, avec support bilingue FR/EN. L'objectif est un SEO parfait et un développement rapide via des composants prêts à l'emploi (Nuxt UI v3).
|
||||||
|
|
||||||
**Core Value:** Chaque page du portfolio doit être crawlable par les moteurs de recherche sans JavaScript côté client — le SSR est la raison d'être de cette migration.
|
**Core Value:** Chaque page du portfolio doit être crawlable par les moteurs de recherche sans JavaScript côté client — le SSR est la raison d'être de cette migration.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ A modern, responsive personal portfolio website showcasing professional skills,
|
|||||||
|
|
||||||
## 🎯 Purpose
|
## 🎯 Purpose
|
||||||
|
|
||||||
This portfolio serves as a professional showcase for **Killian Dal Cin**, a Full Stack Developer specializing in modern web development. The website features:
|
This portfolio serves as a professional showcase for **Killian' DAL-CIN**, a Full Stack Developer specializing in modern web development. The website features:
|
||||||
|
|
||||||
- **Professional Presentation**: Clean, modern design highlighting skills and experience
|
- **Professional Presentation**: Clean, modern design highlighting skills and experience
|
||||||
- **Project Showcase**: Interactive gallery of completed projects with detailed case studies
|
- **Project Showcase**: Interactive gallery of completed projects with detailed case studies
|
||||||
@@ -220,7 +220,7 @@ This project is personal portfolio software. Please respect the intellectual pro
|
|||||||
|
|
||||||
## 📧 Contact
|
## 📧 Contact
|
||||||
|
|
||||||
**Killian Dal Cin**
|
**Killian' DAL-CIN**
|
||||||
|
|
||||||
- Email: contact@killiandalcin.fr
|
- Email: contact@killiandalcin.fr
|
||||||
- LinkedIn: [killian-dalcin](https://linkedin.com/in/killian-dal-cin)
|
- LinkedIn: [killian-dalcin](https://linkedin.com/in/killian-dal-cin)
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ const schema = z.object({
|
|||||||
|
|
||||||
type Schema = z.output<typeof schema>
|
type Schema = z.output<typeof schema>
|
||||||
|
|
||||||
const state = reactive<Partial<Schema>>({
|
const state = reactive({
|
||||||
name: undefined,
|
name: '',
|
||||||
email: undefined,
|
email: '',
|
||||||
message: undefined,
|
message: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||||
@@ -29,10 +29,9 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
|||||||
color: 'success',
|
color: 'success',
|
||||||
icon: 'i-lucide-check',
|
icon: 'i-lucide-check',
|
||||||
})
|
})
|
||||||
// Reset form
|
state.name = ''
|
||||||
state.name = undefined
|
state.email = ''
|
||||||
state.email = undefined
|
state.message = ''
|
||||||
state.message = undefined
|
|
||||||
} catch {
|
} catch {
|
||||||
toast.add({
|
toast.add({
|
||||||
title: t('contact.form.error'),
|
title: t('contact.form.error'),
|
||||||
@@ -46,21 +45,49 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UForm :schema="schema" :state="state" class="flex flex-col gap-4" @submit="onSubmit">
|
<UForm :schema="schema" :state="state" class="space-y-5" @submit="onSubmit">
|
||||||
<UFormField :label="t('contact.form.name')" name="name">
|
<UFormField :label="t('contact.form.name')" name="name">
|
||||||
<UInput v-model="state.name" :placeholder="t('contact.form.name')" class="w-full" />
|
<UInput
|
||||||
|
v-model="state.name"
|
||||||
|
:placeholder="t('contact.form.name')"
|
||||||
|
icon="i-lucide-user"
|
||||||
|
size="lg"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<UFormField :label="t('contact.form.email')" name="email">
|
<UFormField :label="t('contact.form.email')" name="email">
|
||||||
<UInput v-model="state.email" type="email" :placeholder="t('contact.form.email')" class="w-full" />
|
<UInput
|
||||||
|
v-model="state.email"
|
||||||
|
type="email"
|
||||||
|
:placeholder="t('contact.form.email')"
|
||||||
|
icon="i-lucide-mail"
|
||||||
|
size="lg"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<UFormField :label="t('contact.form.message')" name="message">
|
<UFormField :label="t('contact.form.message')" name="message">
|
||||||
<UTextarea v-model="state.message" :rows="5" :placeholder="t('contact.form.message')" class="w-full" />
|
<UTextarea
|
||||||
|
v-model="state.message"
|
||||||
|
:rows="6"
|
||||||
|
:placeholder="t('contact.form.message')"
|
||||||
|
size="lg"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<UButton type="submit" :loading="loading" size="lg" class="self-start">
|
<button
|
||||||
|
type="submit"
|
||||||
|
:disabled="loading"
|
||||||
|
class="inline-flex items-center justify-center gap-2 w-full px-6 py-3.5 rounded-xl bg-brand-500 hover:bg-brand-600 disabled:opacity-60 disabled:cursor-not-allowed text-white font-semibold text-sm transition-all duration-200 shadow-lg shadow-brand-500/25 hover:shadow-brand-500/40"
|
||||||
|
>
|
||||||
|
<UIcon v-if="loading" name="i-lucide-loader-2" class="w-4 h-4 animate-spin" />
|
||||||
|
<template v-if="loading">{{ t('contact.form.sending') }}</template>
|
||||||
|
<template v-else>
|
||||||
{{ t('contact.form.submit') }}
|
{{ t('contact.form.submit') }}
|
||||||
</UButton>
|
<UIcon name="i-lucide-send" class="w-4 h-4" />
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
</UForm>
|
</UForm>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const translatedCategory = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<article
|
<article
|
||||||
class="group relative rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 overflow-hidden transition-all duration-300 hover:border-brand-500/30 hover:shadow-xl hover:shadow-brand-500/5 hover:-translate-y-1"
|
class="group relative rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm overflow-hidden transition-all duration-300 hover:border-brand-500/40 hover:shadow-xl hover:shadow-brand-500/10 hover:-translate-y-1.5"
|
||||||
itemscope
|
itemscope
|
||||||
itemtype="https://schema.org/CreativeWork"
|
itemtype="https://schema.org/CreativeWork"
|
||||||
>
|
>
|
||||||
@@ -30,21 +30,26 @@ const translatedCategory = computed(() => {
|
|||||||
format="webp"
|
format="webp"
|
||||||
width="400"
|
width="400"
|
||||||
height="300"
|
height="300"
|
||||||
class="w-full h-48 object-cover transition-transform duration-500 group-hover:scale-105"
|
class="w-full h-52 object-cover transition-transform duration-500 group-hover:scale-105"
|
||||||
itemprop="image"
|
itemprop="image"
|
||||||
/>
|
/>
|
||||||
<!-- Overlay on hover -->
|
<!-- Gradient overlay -->
|
||||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
<div class="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end justify-center pb-5">
|
||||||
|
<span class="text-white text-sm font-semibold flex items-center gap-1.5 translate-y-2 group-hover:translate-y-0 transition-transform duration-300">
|
||||||
|
{{ t('projects.buttons.viewProject') }}
|
||||||
|
<UIcon name="i-lucide-arrow-right" class="w-4 h-4" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="p-5 flex flex-col gap-3">
|
<div class="p-5 sm:p-6 flex flex-col gap-3">
|
||||||
<!-- Category & Date -->
|
<!-- Category & Date -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<UBadge v-if="project.category" color="primary" variant="subtle" itemprop="genre">
|
<UBadge v-if="project.category" color="primary" variant="subtle" itemprop="genre">
|
||||||
{{ translatedCategory }}
|
{{ translatedCategory }}
|
||||||
</UBadge>
|
</UBadge>
|
||||||
<time v-if="project.date" class="text-xs text-gray-400 dark:text-gray-500 font-medium" :datetime="project.date" itemprop="dateCreated">
|
<time v-if="project.date" class="text-xs text-gray-400 dark:text-gray-500 font-mono" :datetime="project.date" itemprop="dateCreated">
|
||||||
{{ project.date }}
|
{{ project.date }}
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,29 +65,26 @@ const translatedCategory = computed(() => {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Technologies -->
|
<!-- Technologies -->
|
||||||
<div v-if="project.technologies?.length" class="flex flex-wrap gap-1.5 pt-1" itemprop="keywords">
|
<div v-if="project.technologies?.length" class="flex flex-wrap gap-1.5 pt-2" itemprop="keywords">
|
||||||
<span
|
<span
|
||||||
v-for="tech in project.technologies.slice(0, 3)"
|
v-for="tech in project.technologies.slice(0, 3)"
|
||||||
:key="tech"
|
:key="tech"
|
||||||
class="text-xs px-2 py-0.5 rounded-md bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 font-medium"
|
class="text-[11px] px-2.5 py-1 rounded-full bg-gray-100 dark:bg-gray-800/80 text-gray-600 dark:text-gray-400 font-medium border border-gray-200/50 dark:border-gray-700/30"
|
||||||
>
|
>
|
||||||
{{ tech }}
|
{{ tech }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="project.technologies.length > 3" class="text-xs px-2 py-0.5 rounded-md bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-500 font-medium">
|
<span v-if="project.technologies.length > 3" class="text-[11px] px-2.5 py-1 rounded-full bg-gray-100 dark:bg-gray-800/80 text-gray-500 dark:text-gray-500 font-medium border border-gray-200/50 dark:border-gray-700/30">
|
||||||
+{{ project.technologies.length - 3 }}
|
+{{ project.technologies.length - 3 }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Action link -->
|
<!-- Hidden SEO link -->
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="`/project/${project.id}`"
|
:to="`/project/${project.id}`"
|
||||||
class="inline-flex items-center gap-1.5 text-sm font-semibold text-brand-600 dark:text-brand-400 hover:text-brand-700 dark:hover:text-brand-300 transition-colors mt-1 group/link"
|
class="absolute inset-0 z-10"
|
||||||
:aria-label="`${t('projects.buttons.viewProject')} - ${project.title}`"
|
:aria-label="`${t('projects.buttons.viewProject')} - ${project.title}`"
|
||||||
itemprop="url"
|
itemprop="url"
|
||||||
>
|
/>
|
||||||
{{ t('projects.buttons.viewProject') }}
|
|
||||||
<UIcon name="i-lucide-arrow-right" class="w-4 h-4 transition-transform group-hover/link:translate-x-0.5" />
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ defineExpose({ openGallery })
|
|||||||
<template>
|
<template>
|
||||||
<UModal v-model:open="isOpen" fullscreen>
|
<UModal v-model:open="isOpen" fullscreen>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="flex flex-col items-center justify-center h-full p-4 gap-4">
|
<div class="flex flex-col items-center justify-center h-full p-4 gap-4" @click.self="isOpen = false">
|
||||||
<div class="flex items-center justify-between w-full max-w-4xl">
|
<div class="flex items-center justify-between w-full max-w-4xl">
|
||||||
<h3 class="text-lg font-semibold">{{ projectTitle }}</h3>
|
<h3 class="text-lg font-semibold">{{ projectTitle }}</h3>
|
||||||
<UButton
|
<UButton
|
||||||
|
|||||||
@@ -18,70 +18,73 @@ const quickLinks = computed(() => [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<footer class="border-t border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900/50">
|
<footer class="border-t border-gray-200/80 dark:border-gray-800/50 bg-gray-50/80 dark:bg-gray-950/80">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-16">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 md:py-20">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-10 md:gap-8">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 lg:gap-8">
|
||||||
<!-- Branding & Tagline -->
|
<!-- Brand column -->
|
||||||
<div class="space-y-4">
|
<div class="sm:col-span-2 lg:col-span-1 space-y-5">
|
||||||
<NuxtLink :to="localePath('/')" class="flex items-center gap-2.5">
|
<NuxtLink :to="localePath('/')" class="flex items-center gap-2.5 group">
|
||||||
<NuxtImg
|
<NuxtImg src="/images/logo.webp" alt="Killian' DAL-CIN" width="36" height="36" loading="lazy"
|
||||||
src="/images/logo.webp"
|
class="rounded-lg transition-transform duration-300 group-hover:scale-110" />
|
||||||
alt="Killian Dalcin"
|
<span class="text-lg font-bold text-gray-900 dark:text-white">Killian' DAL-CIN</span>
|
||||||
width="36"
|
|
||||||
height="36"
|
|
||||||
loading="lazy"
|
|
||||||
class="rounded-lg"
|
|
||||||
/>
|
|
||||||
<span class="text-lg font-bold text-gray-900 dark:text-white">Killian Dalcin</span>
|
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 leading-relaxed max-w-xs">
|
<p class="text-sm text-gray-500 dark:text-gray-400 leading-relaxed max-w-xs">
|
||||||
Full Stack Developer & Hytale Plugin Developer. Building modern web experiences and game plugins.
|
Full Stack Developer & Hytale Plugin Developer. Building modern web experiences and game plugins.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Quick Links -->
|
<!-- Navigation links -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white uppercase tracking-wider mb-4">
|
<h3 class="font-mono text-xs text-gray-400 dark:text-gray-500 uppercase tracking-widest mb-5">
|
||||||
Navigation
|
Navigation
|
||||||
</h3>
|
</h3>
|
||||||
<nav class="flex flex-col gap-2.5">
|
<nav class="flex flex-col gap-3">
|
||||||
<NuxtLink
|
<NuxtLink v-for="link in quickLinks" :key="link.key" :to="localePath(link.path)"
|
||||||
v-for="link in quickLinks"
|
class="text-sm text-gray-600 dark:text-gray-400 hover:text-brand-500 dark:hover:text-brand-400 transition-colors duration-200">
|
||||||
:key="link.key"
|
|
||||||
:to="localePath(link.path)"
|
|
||||||
class="text-sm text-gray-500 dark:text-gray-400 hover:text-brand-500 dark:hover:text-brand-400 transition-colors"
|
|
||||||
>
|
|
||||||
{{ t(`nav.${link.key}`) }}
|
{{ t(`nav.${link.key}`) }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Social Links -->
|
<!-- Services links -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white uppercase tracking-wider mb-4">
|
<h3 class="font-mono text-xs text-gray-400 dark:text-gray-500 uppercase tracking-widest mb-5">
|
||||||
Social
|
Services
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex items-center gap-3">
|
<nav class="flex flex-col gap-3">
|
||||||
<a
|
<span class="text-sm text-gray-600 dark:text-gray-400">Web Development</span>
|
||||||
v-for="link in socialLinks"
|
<span class="text-sm text-gray-600 dark:text-gray-400">Hytale Plugins</span>
|
||||||
:key="link.name"
|
<span class="text-sm text-gray-600 dark:text-gray-400">Consulting</span>
|
||||||
:href="link.url"
|
<span class="text-sm text-gray-600 dark:text-gray-400">Maintenance</span>
|
||||||
target="_blank"
|
</nav>
|
||||||
rel="noopener noreferrer"
|
</div>
|
||||||
|
|
||||||
|
<!-- Connect -->
|
||||||
|
<div>
|
||||||
|
<h3 class="font-mono text-xs text-gray-400 dark:text-gray-500 uppercase tracking-widest mb-5">
|
||||||
|
Connect
|
||||||
|
</h3>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<a v-for="link in socialLinks" :key="link.name" :href="link.url" target="_blank" rel="noopener noreferrer"
|
||||||
:aria-label="t(link.ariaKey)"
|
:aria-label="t(link.ariaKey)"
|
||||||
class="w-10 h-10 inline-flex items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-brand-500/10 dark:hover:bg-brand-500/10 transition-all duration-200 group"
|
class="w-10 h-10 inline-flex items-center justify-center rounded-xl border border-gray-200/80 dark:border-gray-800/50 bg-white/60 dark:bg-gray-900/40 hover:border-brand-500/40 hover:bg-brand-500/10 dark:hover:bg-brand-500/10 transition-all duration-300 group">
|
||||||
>
|
<UIcon :name="link.icon"
|
||||||
<UIcon :name="link.icon" class="w-5 h-5 text-gray-500 dark:text-gray-400 group-hover:text-brand-500 dark:group-hover:text-brand-400 transition-colors" />
|
class="w-4.5 h-4.5 text-gray-500 dark:text-gray-400 group-hover:text-brand-500 dark:group-hover:text-brand-400 transition-colors" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom bar -->
|
<!-- Bottom bar -->
|
||||||
<div class="mt-10 pt-6 border-t border-gray-200 dark:border-gray-800">
|
<div
|
||||||
<p class="text-sm text-gray-400 dark:text-gray-500 text-center">
|
class="mt-14 pt-8 border-t border-gray-200/60 dark:border-gray-800/40 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<p class="text-sm text-gray-400 dark:text-gray-500 font-mono">
|
||||||
{{ t('footer.copyright') }}
|
{{ t('footer.copyright') }}
|
||||||
</p>
|
</p>
|
||||||
|
<div class="flex items-center gap-1.5 text-xs text-gray-400 dark:text-gray-600">
|
||||||
|
<span class="w-1.5 h-1.5 rounded-full bg-brand-500 animate-pulse" />
|
||||||
|
<span>Built with Nuxt</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -27,40 +27,26 @@ function isActive(path: string): boolean {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="sticky top-0 z-50 backdrop-blur-xl bg-white/80 dark:bg-gray-950/80 border-b border-gray-200/50 dark:border-gray-800/50">
|
<header
|
||||||
|
class="sticky top-0 z-50 backdrop-blur-xl bg-white/80 dark:bg-gray-950/80 border-b border-gray-200/50 dark:border-gray-800/50">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="flex items-center justify-between h-16">
|
<div class="flex items-center justify-between h-16">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<NuxtLink
|
<NuxtLink :to="localePath('/')" :aria-label="t('a11y.logoLabel')" class="flex items-center gap-2.5 shrink-0">
|
||||||
:to="localePath('/')"
|
<NuxtImg src="/images/logo.webp" alt="Killian' DAL-CIN" width="36" height="36" loading="eager"
|
||||||
:aria-label="t('a11y.logoLabel')"
|
class="rounded-lg" />
|
||||||
class="flex items-center gap-2.5 shrink-0"
|
<span class="text-base font-semibold tracking-tight text-gray-900 dark:text-white">Killian'</span>
|
||||||
>
|
|
||||||
<NuxtImg
|
|
||||||
src="/images/logo.webp"
|
|
||||||
alt="Killian Dalcin"
|
|
||||||
width="36"
|
|
||||||
height="36"
|
|
||||||
loading="eager"
|
|
||||||
class="rounded-lg"
|
|
||||||
/>
|
|
||||||
<span class="text-base font-semibold tracking-tight text-gray-900 dark:text-white">Killian</span>
|
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
||||||
<!-- Desktop nav -->
|
<!-- Desktop nav -->
|
||||||
<nav class="hidden md:flex items-center gap-1" aria-label="Main navigation">
|
<nav class="hidden md:flex items-center gap-1" aria-label="Main navigation">
|
||||||
<NuxtLink
|
<NuxtLink v-for="link in navLinks" :key="link.key" :to="localePath(link.path)"
|
||||||
v-for="link in navLinks"
|
|
||||||
:key="link.key"
|
|
||||||
:to="localePath(link.path)"
|
|
||||||
:aria-current="isActive(link.path) ? 'page' : undefined"
|
:aria-current="isActive(link.path) ? 'page' : undefined"
|
||||||
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors"
|
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors" :class="[
|
||||||
:class="[
|
|
||||||
isActive(link.path)
|
isActive(link.path)
|
||||||
? 'text-brand-600 dark:text-brand-400 bg-brand-50 dark:bg-brand-950/40'
|
? 'text-brand-600 dark:text-brand-400 bg-brand-50 dark:bg-brand-950/40'
|
||||||
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800/60'
|
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800/60'
|
||||||
]"
|
]">
|
||||||
>
|
|
||||||
{{ t(`nav.${link.key}`) }}
|
{{ t(`nav.${link.key}`) }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -68,36 +54,19 @@ function isActive(path: string): boolean {
|
|||||||
<!-- Right actions -->
|
<!-- Right actions -->
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<!-- Language toggle -->
|
<!-- Language toggle -->
|
||||||
<UButton
|
<UButton variant="ghost" color="neutral" size="sm" :aria-label="t('a11y.langToggle')" @click="toggleLocale">
|
||||||
variant="ghost"
|
|
||||||
color="neutral"
|
|
||||||
size="sm"
|
|
||||||
:aria-label="t('a11y.langToggle')"
|
|
||||||
@click="toggleLocale"
|
|
||||||
>
|
|
||||||
{{ locale === 'fr' ? 'EN' : 'FR' }}
|
{{ locale === 'fr' ? 'EN' : 'FR' }}
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
||||||
<!-- Theme toggle -->
|
<!-- Theme toggle -->
|
||||||
<UButton
|
<UButton variant="ghost" color="neutral" size="sm"
|
||||||
variant="ghost"
|
|
||||||
color="neutral"
|
|
||||||
size="sm"
|
|
||||||
:icon="colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon'"
|
:icon="colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon'"
|
||||||
:aria-label="colorMode.value === 'dark' ? t('a11y.themeDark') : t('a11y.themeLight')"
|
:aria-label="colorMode.value === 'dark' ? t('a11y.themeDark') : t('a11y.themeLight')"
|
||||||
@click="toggleTheme"
|
@click="toggleTheme" />
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Mobile hamburger -->
|
<!-- Mobile hamburger -->
|
||||||
<UButton
|
<UButton variant="ghost" color="neutral" size="sm" icon="i-lucide-menu" class="md:hidden"
|
||||||
variant="ghost"
|
:aria-label="t('a11y.openMenu')" @click="mobileOpen = true" />
|
||||||
color="neutral"
|
|
||||||
size="sm"
|
|
||||||
icon="i-lucide-menu"
|
|
||||||
class="md:hidden"
|
|
||||||
:aria-label="t('a11y.openMenu')"
|
|
||||||
@click="mobileOpen = true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -106,32 +75,20 @@ function isActive(path: string): boolean {
|
|||||||
<USlideover v-model:open="mobileOpen" side="left" class="md:hidden">
|
<USlideover v-model:open="mobileOpen" side="left" class="md:hidden">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-2.5">
|
<div class="flex items-center gap-2.5">
|
||||||
<NuxtImg
|
<NuxtImg src="/images/logo.webp" alt="Killian' DAL-CIN" width="32" height="32" class="rounded-lg" />
|
||||||
src="/images/logo.webp"
|
|
||||||
alt="Killian Dalcin"
|
|
||||||
width="32"
|
|
||||||
height="32"
|
|
||||||
class="rounded-lg"
|
|
||||||
/>
|
|
||||||
<span class="text-base font-semibold text-gray-900 dark:text-white">Killian</span>
|
<span class="text-base font-semibold text-gray-900 dark:text-white">Killian</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
<nav class="flex flex-col gap-1" aria-label="Mobile navigation">
|
<nav class="flex flex-col gap-1" aria-label="Mobile navigation">
|
||||||
<NuxtLink
|
<NuxtLink v-for="link in navLinks" :key="link.key" :to="localePath(link.path)"
|
||||||
v-for="link in navLinks"
|
|
||||||
:key="link.key"
|
|
||||||
:to="localePath(link.path)"
|
|
||||||
:aria-current="isActive(link.path) ? 'page' : undefined"
|
:aria-current="isActive(link.path) ? 'page' : undefined"
|
||||||
class="px-4 py-3 text-base font-medium rounded-lg transition-colors"
|
class="px-4 py-3 text-base font-medium rounded-lg transition-colors" :class="[
|
||||||
:class="[
|
|
||||||
isActive(link.path)
|
isActive(link.path)
|
||||||
? 'text-brand-600 dark:text-brand-400 bg-brand-50 dark:bg-brand-950/40'
|
? 'text-brand-600 dark:text-brand-400 bg-brand-50 dark:bg-brand-950/40'
|
||||||
: 'text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800/60'
|
: 'text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800/60'
|
||||||
]"
|
]" @click="mobileOpen = false">
|
||||||
@click="mobileOpen = false"
|
|
||||||
>
|
|
||||||
{{ t(`nav.${link.key}`) }}
|
{{ t(`nav.${link.key}`) }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -139,21 +96,12 @@ function isActive(path: string): boolean {
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<UButton
|
<UButton variant="ghost" color="neutral" :aria-label="t('a11y.langToggle')" @click="toggleLocale">
|
||||||
variant="ghost"
|
|
||||||
color="neutral"
|
|
||||||
:aria-label="t('a11y.langToggle')"
|
|
||||||
@click="toggleLocale"
|
|
||||||
>
|
|
||||||
{{ locale === 'fr' ? 'EN' : 'FR' }}
|
{{ locale === 'fr' ? 'EN' : 'FR' }}
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton
|
<UButton variant="ghost" color="neutral" :icon="colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon'"
|
||||||
variant="ghost"
|
|
||||||
color="neutral"
|
|
||||||
:icon="colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon'"
|
|
||||||
:aria-label="colorMode.value === 'dark' ? t('a11y.themeDark') : t('a11y.themeLight')"
|
:aria-label="colorMode.value === 'dark' ? t('a11y.themeDark') : t('a11y.themeLight')"
|
||||||
@click="toggleTheme"
|
@click="toggleTheme" />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</USlideover>
|
</USlideover>
|
||||||
|
|||||||
@@ -1,26 +1,62 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const localePath = useLocalePath()
|
const localePath = useLocalePath()
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title?: string
|
||||||
|
subtitle?: string
|
||||||
|
primaryText?: string
|
||||||
|
primaryTo?: string
|
||||||
|
secondaryText?: string
|
||||||
|
secondaryTo?: string
|
||||||
|
external?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
title: '',
|
||||||
|
subtitle: '',
|
||||||
|
primaryText: '',
|
||||||
|
primaryTo: '/contact',
|
||||||
|
secondaryText: '',
|
||||||
|
secondaryTo: '/about',
|
||||||
|
external: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const resolvedTitle = computed(() => props.title || t('home.cta2.title'))
|
||||||
|
const resolvedSubtitle = computed(() => props.subtitle || t('home.cta2.subtitle'))
|
||||||
|
const resolvedPrimaryText = computed(() => props.primaryText || t('home.cta2.startProject'))
|
||||||
|
const resolvedSecondaryText = computed(() => props.secondaryText || t('home.cta2.learnMore'))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="py-20 md:py-28 px-4">
|
<section class="py-24 md:py-32 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-5xl mx-auto">
|
<div class="max-w-5xl mx-auto">
|
||||||
<div class="relative overflow-hidden rounded-3xl bg-gradient-to-br from-brand-600 via-brand-500 to-emerald-500 px-8 py-16 sm:px-16 sm:py-20 text-center">
|
<div class="relative overflow-hidden rounded-3xl px-8 py-20 sm:px-16 sm:py-24 text-center border border-gray-200/60 dark:border-gray-800/40 bg-gray-50 dark:bg-gray-900">
|
||||||
<!-- Decorative shapes -->
|
<!-- Subtle dot pattern -->
|
||||||
<div class="absolute top-0 left-0 w-72 h-72 bg-white/10 rounded-full -translate-x-1/2 -translate-y-1/2 blur-2xl" aria-hidden="true" />
|
<div class="absolute inset-0 opacity-[0.03] dark:opacity-[0.06]" aria-hidden="true"
|
||||||
<div class="absolute bottom-0 right-0 w-96 h-96 bg-black/10 rounded-full translate-x-1/3 translate-y-1/3 blur-2xl" aria-hidden="true" />
|
style="background-image: radial-gradient(circle, currentColor 1px, transparent 1px); background-size: 24px 24px;" />
|
||||||
|
<!-- Brand glow -->
|
||||||
|
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-[600px] h-[300px] bg-brand-500/8 dark:bg-brand-500/15 rounded-full blur-3xl pointer-events-none" aria-hidden="true" />
|
||||||
|
|
||||||
<div class="relative z-10">
|
<div class="relative z-10">
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">{{ t('home.cta2.title') }}</h2>
|
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white mb-5 tracking-tight">{{ resolvedTitle }}</h2>
|
||||||
<p class="text-lg text-white/80 mb-10 max-w-2xl mx-auto">{{ t('home.cta2.subtitle') }}</p>
|
<p class="text-lg sm:text-xl text-gray-500 dark:text-gray-400 mb-12 max-w-2xl mx-auto leading-relaxed">{{ resolvedSubtitle }}</p>
|
||||||
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
<UButton :to="localePath('/contact')" size="xl" color="white" class="font-semibold">
|
<NuxtLink
|
||||||
{{ t('home.cta2.startProject') }}
|
:to="external ? props.primaryTo : localePath(props.primaryTo)"
|
||||||
</UButton>
|
:target="external ? '_blank' : undefined"
|
||||||
<UButton :to="localePath('/about')" size="xl" variant="outline" color="white" class="font-semibold">
|
:rel="external ? 'noopener noreferrer' : undefined"
|
||||||
{{ t('home.cta2.learnMore') }}
|
class="inline-flex items-center justify-center gap-2 px-7 py-3.5 rounded-xl bg-brand-500 hover:bg-brand-600 text-white font-semibold text-sm transition-all duration-200 shadow-lg shadow-brand-500/25 hover:shadow-brand-500/40"
|
||||||
</UButton>
|
>
|
||||||
|
{{ resolvedPrimaryText }}
|
||||||
|
<UIcon :name="external ? 'i-lucide-external-link' : 'i-lucide-arrow-right'" class="w-4 h-4" />
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink
|
||||||
|
:to="localePath(props.secondaryTo)"
|
||||||
|
class="inline-flex items-center justify-center gap-2 px-7 py-3.5 rounded-xl border border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:border-brand-500/50 hover:text-brand-500 font-semibold text-sm transition-all duration-200"
|
||||||
|
>
|
||||||
|
{{ resolvedSecondaryText }}
|
||||||
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ const items = computed(() =>
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="py-20 md:py-28 px-4">
|
<section class="py-24 md:py-32 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-3xl mx-auto">
|
<div class="max-w-3xl mx-auto">
|
||||||
<div class="text-center mb-14">
|
<div class="text-center mb-16">
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">FAQ</span>
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// faq</span>
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ title }}</h2>
|
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold mt-3 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">{{ title }}</h2>
|
||||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3">{{ subtitle }}</p>
|
<p class="text-lg text-gray-500 dark:text-gray-400 mt-4 leading-relaxed">{{ subtitle }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-2">
|
<div class="rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-3 sm:p-4 shadow-sm">
|
||||||
<UAccordion :items="items" type="single" collapsible />
|
<UAccordion :items="items" type="single" collapsible />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,27 +4,31 @@ const { featuredProjects } = useProjects()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="py-20 md:py-28 px-4">
|
<section class="py-24 md:py-32 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="max-w-7xl mx-auto">
|
||||||
<!-- Section header -->
|
<!-- Section header -->
|
||||||
<div class="flex flex-col md:flex-row md:items-end md:justify-between gap-4 mb-14">
|
<div class="flex flex-col md:flex-row md:items-end md:justify-between gap-6 mb-16">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Portfolio</span>
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// portfolio</span>
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('home.featuredProjects.title') }}</h2>
|
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold mt-3 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">{{ t('home.featuredProjects.title') }}</h2>
|
||||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3 max-w-2xl">{{ t('home.featuredProjects.subtitle') }}</p>
|
<p class="text-lg text-gray-500 dark:text-gray-400 mt-4 max-w-2xl leading-relaxed">{{ t('home.featuredProjects.subtitle') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<UButton to="/projects" variant="ghost" trailing-icon="i-lucide-arrow-right" class="shrink-0 self-start md:self-auto">
|
<UButton to="/projects" variant="ghost" trailing-icon="i-lucide-arrow-right" class="shrink-0 self-start md:self-auto group">
|
||||||
{{ t('home.cta.viewProjects') }}
|
{{ t('home.cta.viewProjects') }}
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Projects grid -->
|
<!-- Bento grid -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 lg:gap-6 auto-rows-fr">
|
||||||
<ProjectCard
|
<div
|
||||||
v-for="project in featuredProjects"
|
v-for="(project, index) in featuredProjects"
|
||||||
:key="project.id"
|
:key="project.id"
|
||||||
:project="project"
|
:class="[
|
||||||
/>
|
index === 0 ? 'md:col-span-2 md:row-span-1' : '',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<ProjectCard :project="project" :class="{ 'h-full': true }" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -7,18 +7,22 @@ const localePath = useLocalePath()
|
|||||||
<section class="relative min-h-[80vh] flex items-center overflow-hidden bg-white dark:bg-gray-950">
|
<section class="relative min-h-[80vh] flex items-center overflow-hidden bg-white dark:bg-gray-950">
|
||||||
<!-- Dot grid background pattern -->
|
<!-- Dot grid background pattern -->
|
||||||
<div class="absolute inset-0 opacity-[0.03] dark:opacity-[0.06]" aria-hidden="true">
|
<div class="absolute inset-0 opacity-[0.03] dark:opacity-[0.06]" aria-hidden="true">
|
||||||
<div class="absolute inset-0" style="background-image: radial-gradient(circle, currentColor 1px, transparent 1px); background-size: 32px 32px;" />
|
<div class="absolute inset-0"
|
||||||
|
style="background-image: radial-gradient(circle, currentColor 1px, transparent 1px); background-size: 32px 32px;" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Gradient glow -->
|
<!-- Gradient glow -->
|
||||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[600px] bg-brand-500/5 dark:bg-brand-500/10 rounded-full blur-3xl pointer-events-none" aria-hidden="true" />
|
<div
|
||||||
|
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[600px] bg-brand-500/5 dark:bg-brand-500/10 rounded-full blur-3xl pointer-events-none"
|
||||||
|
aria-hidden="true" />
|
||||||
|
|
||||||
<div class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full py-16 md:py-24">
|
<div class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full py-16 md:py-24">
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
|
||||||
<!-- Left: Content -->
|
<!-- Left: Content -->
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<!-- Status badge -->
|
<!-- Status badge -->
|
||||||
<div class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-brand-500/10 dark:bg-brand-500/15 border border-brand-500/20">
|
<div
|
||||||
|
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-brand-500/10 dark:bg-brand-500/15 border border-brand-500/20">
|
||||||
<span class="relative flex h-2 w-2">
|
<span class="relative flex h-2 w-2">
|
||||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-brand-400 opacity-75" />
|
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-brand-400 opacity-75" />
|
||||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-brand-500" />
|
<span class="relative inline-flex rounded-full h-2 w-2 bg-brand-500" />
|
||||||
@@ -28,8 +32,11 @@ const localePath = useLocalePath()
|
|||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h1 class="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-extrabold tracking-tight leading-[1.1]">
|
<h1 class="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-extrabold tracking-tight leading-[1.1]">
|
||||||
<span class="text-gray-900 dark:text-white">{{ t('home.title').split(' ').slice(0, -2).join(' ') }} </span>
|
<span class="text-gray-900 dark:text-white">{{ t('home.title').split(' ').slice(0, -2).join(' ') }}
|
||||||
<span class="bg-gradient-to-r from-brand-500 via-brand-400 to-emerald-400 bg-clip-text text-transparent">{{ t('home.title').split(' ').slice(-2).join(' ') }}</span>
|
</span>
|
||||||
|
<span
|
||||||
|
class="bg-gradient-to-r from-brand-500 via-brand-400 to-emerald-400 bg-clip-text text-transparent">{{
|
||||||
|
t('home.title').split(' ').slice(-2).join(' ') }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-lg sm:text-xl text-gray-600 dark:text-gray-400 max-w-xl leading-relaxed">
|
<p class="text-lg sm:text-xl text-gray-600 dark:text-gray-400 max-w-xl leading-relaxed">
|
||||||
{{ t('home.subtitle') }}
|
{{ t('home.subtitle') }}
|
||||||
@@ -38,15 +45,27 @@ const localePath = useLocalePath()
|
|||||||
|
|
||||||
<!-- CTA Buttons -->
|
<!-- CTA Buttons -->
|
||||||
<div class="flex flex-col sm:flex-row gap-3">
|
<div class="flex flex-col sm:flex-row gap-3">
|
||||||
<UButton :to="localePath('/projects')" size="xl" icon="i-lucide-arrow-right" trailing class="font-semibold">
|
<NuxtLink
|
||||||
|
:to="localePath('/projects')"
|
||||||
|
class="inline-flex items-center justify-center gap-2 px-6 py-3 rounded-xl bg-brand-500 hover:bg-brand-600 text-white font-semibold text-sm transition-all duration-200 shadow-lg shadow-brand-500/25 hover:shadow-brand-500/40"
|
||||||
|
>
|
||||||
{{ t('home.cta.viewProjects') }}
|
{{ t('home.cta.viewProjects') }}
|
||||||
</UButton>
|
<UIcon name="i-lucide-arrow-right" class="w-4 h-4" />
|
||||||
<UButton :to="localePath('/fiverr')" size="xl" variant="outline" icon="i-lucide-dollar-sign" trailing class="font-semibold">
|
</NuxtLink>
|
||||||
|
<NuxtLink
|
||||||
|
:to="localePath('/fiverr')"
|
||||||
|
class="inline-flex items-center justify-center gap-2 px-6 py-3 rounded-xl border border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:border-brand-500/50 hover:text-brand-500 font-semibold text-sm transition-all duration-200"
|
||||||
|
>
|
||||||
{{ t('nav.fiverr') }}
|
{{ t('nav.fiverr') }}
|
||||||
</UButton>
|
<UIcon name="i-lucide-external-link" class="w-4 h-4" />
|
||||||
<UButton :to="localePath('/contact')" size="xl" variant="ghost" icon="i-lucide-message-circle" trailing class="font-semibold">
|
</NuxtLink>
|
||||||
|
<NuxtLink
|
||||||
|
:to="localePath('/contact')"
|
||||||
|
class="inline-flex items-center justify-center gap-2 px-6 py-3 rounded-xl text-gray-600 dark:text-gray-400 hover:text-brand-500 font-semibold text-sm transition-all duration-200"
|
||||||
|
>
|
||||||
{{ t('home.cta.contactMe') }}
|
{{ t('home.cta.contactMe') }}
|
||||||
</UButton>
|
<UIcon name="i-lucide-message-circle" class="w-4 h-4" />
|
||||||
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -54,9 +73,11 @@ const localePath = useLocalePath()
|
|||||||
<div class="hidden lg:block" aria-hidden="true">
|
<div class="hidden lg:block" aria-hidden="true">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<!-- Terminal window -->
|
<!-- Terminal window -->
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900 shadow-2xl shadow-brand-500/5 overflow-hidden">
|
<div
|
||||||
|
class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900 shadow-2xl shadow-brand-500/5 overflow-hidden">
|
||||||
<!-- Title bar -->
|
<!-- Title bar -->
|
||||||
<div class="flex items-center gap-2 px-4 py-3 bg-gray-100 dark:bg-gray-800/80 border-b border-gray-200 dark:border-gray-700/50">
|
<div
|
||||||
|
class="flex items-center gap-2 px-4 py-3 bg-gray-100 dark:bg-gray-800/80 border-b border-gray-200 dark:border-gray-700/50">
|
||||||
<div class="flex gap-1.5">
|
<div class="flex gap-1.5">
|
||||||
<div class="w-3 h-3 rounded-full bg-red-400/80" />
|
<div class="w-3 h-3 rounded-full bg-red-400/80" />
|
||||||
<div class="w-3 h-3 rounded-full bg-yellow-400/80" />
|
<div class="w-3 h-3 rounded-full bg-yellow-400/80" />
|
||||||
@@ -75,11 +96,13 @@ const localePath = useLocalePath()
|
|||||||
</div>
|
</div>
|
||||||
<div class="pl-6">
|
<div class="pl-6">
|
||||||
<span class="text-purple-500 dark:text-purple-400">name</span><span class="text-gray-500">: </span>
|
<span class="text-purple-500 dark:text-purple-400">name</span><span class="text-gray-500">: </span>
|
||||||
<span class="text-amber-600 dark:text-amber-400">'Killian Dalcin'</span><span class="text-gray-500">,</span>
|
<span class="text-amber-600 dark:text-amber-400">'Killian\' DAL-CIN'</span><span
|
||||||
|
class="text-gray-500">,</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-6">
|
<div class="pl-6">
|
||||||
<span class="text-purple-500 dark:text-purple-400">role</span><span class="text-gray-500">: </span>
|
<span class="text-purple-500 dark:text-purple-400">role</span><span class="text-gray-500">: </span>
|
||||||
<span class="text-amber-600 dark:text-amber-400">'Full Stack Dev'</span><span class="text-gray-500">,</span>
|
<span class="text-amber-600 dark:text-amber-400">'Full Stack Dev'</span><span
|
||||||
|
class="text-gray-500">,</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-6">
|
<div class="pl-6">
|
||||||
<span class="text-purple-500 dark:text-purple-400">skills</span><span class="text-gray-500">: [</span>
|
<span class="text-purple-500 dark:text-purple-400">skills</span><span class="text-gray-500">: [</span>
|
||||||
@@ -91,7 +114,8 @@ const localePath = useLocalePath()
|
|||||||
</div>
|
</div>
|
||||||
<div class="pl-10">
|
<div class="pl-10">
|
||||||
<span class="text-amber-600 dark:text-amber-400">'Java'</span><span class="text-gray-500">, </span>
|
<span class="text-amber-600 dark:text-amber-400">'Java'</span><span class="text-gray-500">, </span>
|
||||||
<span class="text-amber-600 dark:text-amber-400">'Hytale Plugins'</span><span class="text-gray-500">,</span>
|
<span class="text-amber-600 dark:text-amber-400">'Hytale Plugins'</span><span
|
||||||
|
class="text-gray-500">,</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-10">
|
<div class="pl-10">
|
||||||
<span class="text-amber-600 dark:text-amber-400">'Docker'</span><span class="text-gray-500">, </span>
|
<span class="text-amber-600 dark:text-amber-400">'Docker'</span><span class="text-gray-500">, </span>
|
||||||
@@ -101,7 +125,8 @@ const localePath = useLocalePath()
|
|||||||
<span class="text-gray-500">],</span>
|
<span class="text-gray-500">],</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-6">
|
<div class="pl-6">
|
||||||
<span class="text-purple-500 dark:text-purple-400">available</span><span class="text-gray-500">: </span>
|
<span class="text-purple-500 dark:text-purple-400">available</span><span class="text-gray-500">:
|
||||||
|
</span>
|
||||||
<span class="text-brand-500">true</span>
|
<span class="text-brand-500">true</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -116,11 +141,13 @@ const localePath = useLocalePath()
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Floating decoration cards -->
|
<!-- Floating decoration cards -->
|
||||||
<div class="absolute -top-4 -right-4 px-3 py-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-lg text-xs font-medium flex items-center gap-2">
|
<div
|
||||||
|
class="absolute -top-4 -right-4 px-3 py-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-lg text-xs font-medium flex items-center gap-2">
|
||||||
<span class="w-2 h-2 rounded-full bg-brand-500" />
|
<span class="w-2 h-2 rounded-full bg-brand-500" />
|
||||||
<span class="text-gray-700 dark:text-gray-300">50+ projects</span>
|
<span class="text-gray-700 dark:text-gray-300">50+ projects</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute -bottom-3 -left-3 px-3 py-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-lg text-xs font-medium flex items-center gap-2">
|
<div
|
||||||
|
class="absolute -bottom-3 -left-3 px-3 py-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-lg text-xs font-medium flex items-center gap-2">
|
||||||
<UIcon name="i-lucide-star" class="text-yellow-400 w-3.5 h-3.5" />
|
<UIcon name="i-lucide-star" class="text-yellow-400 w-3.5 h-3.5" />
|
||||||
<span class="text-gray-700 dark:text-gray-300">5.0 rating</span>
|
<span class="text-gray-700 dark:text-gray-300">5.0 rating</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,29 +26,38 @@ const services = computed(() => [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="py-20 md:py-28 px-4 bg-gray-50 dark:bg-gray-900/50 rounded-3xl mx-2 md:mx-0">
|
<section class="py-24 md:py-32 px-4 sm:px-6 lg:px-8 relative overflow-hidden">
|
||||||
<div class="max-w-7xl mx-auto">
|
<!-- Subtle background gradient -->
|
||||||
<div class="text-center mb-14">
|
<div class="absolute inset-0 bg-gray-50/80 dark:bg-gray-900/40" aria-hidden="true" />
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Services</span>
|
<div class="absolute top-0 right-0 w-[500px] h-[500px] bg-brand-500/5 dark:bg-brand-500/8 rounded-full blur-3xl -translate-y-1/2 translate-x-1/4 pointer-events-none" aria-hidden="true" />
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('home.services.title') }}</h2>
|
|
||||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3 max-w-2xl mx-auto">{{ t('home.services.subtitle') }}</p>
|
<div class="relative z-10 max-w-7xl mx-auto">
|
||||||
|
<div class="text-center mb-16">
|
||||||
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// services</span>
|
||||||
|
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold mt-3 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">{{ t('home.services.title') }}</h2>
|
||||||
|
<p class="text-lg text-gray-500 dark:text-gray-400 mt-4 max-w-2xl mx-auto leading-relaxed">{{ t('home.services.subtitle') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-5 lg:gap-6">
|
||||||
<div
|
<div
|
||||||
v-for="(service, index) in services"
|
v-for="(service, index) in services"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="group relative rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8 transition-all duration-300 hover:border-brand-500/30 hover:shadow-lg hover:shadow-brand-500/5 hover:-translate-y-0.5"
|
class="group relative rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-7 sm:p-8 transition-all duration-300 hover:border-brand-500/40 hover:shadow-xl hover:shadow-brand-500/10 hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
|
<!-- Hover glow effect -->
|
||||||
|
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-brand-500/0 to-emerald-500/0 group-hover:from-brand-500/5 group-hover:to-emerald-500/5 transition-all duration-500 pointer-events-none" aria-hidden="true" />
|
||||||
|
|
||||||
|
<div class="relative z-10">
|
||||||
<!-- Icon -->
|
<!-- Icon -->
|
||||||
<div class="w-12 h-12 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mb-5 transition-colors group-hover:bg-brand-500/20">
|
<div class="w-12 h-12 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mb-6 transition-all duration-300 group-hover:bg-brand-500/20 group-hover:scale-110">
|
||||||
<UIcon :name="service.icon" class="text-brand-600 dark:text-brand-400 text-xl" />
|
<UIcon :name="service.icon" class="text-brand-600 dark:text-brand-400 text-xl" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">{{ service.title }}</h3>
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3 group-hover:text-brand-600 dark:group-hover:text-brand-400 transition-colors">{{ service.title }}</h3>
|
||||||
<p class="text-gray-500 dark:text-gray-400 leading-relaxed">{{ service.description }}</p>
|
<p class="text-gray-500 dark:text-gray-400 leading-relaxed">{{ service.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,40 +5,43 @@ const { t } = useI18n()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="py-20 md:py-28 px-4">
|
<section class="py-24 md:py-32 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="max-w-7xl mx-auto">
|
||||||
<div class="text-center mb-14">
|
<div class="text-center mb-16">
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">{{ t('testimonials.title') }}</span>
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// testimonials</span>
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('testimonials.title') }}</h2>
|
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold mt-3 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">{{ t('testimonials.title') }}</h2>
|
||||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3 max-w-2xl mx-auto">{{ t('testimonials.subtitle') }}</p>
|
<p class="text-lg text-gray-500 dark:text-gray-400 mt-4 max-w-2xl mx-auto leading-relaxed">{{ t('testimonials.subtitle') }}</p>
|
||||||
|
|
||||||
<!-- Stats row -->
|
<!-- Stats row -->
|
||||||
<div class="flex justify-center gap-10 mt-10">
|
<div class="flex justify-center gap-8 sm:gap-12 mt-12">
|
||||||
<div class="text-center">
|
<div class="text-center group">
|
||||||
<p class="text-4xl font-extrabold text-brand-500 dark:text-brand-400">{{ testimonialsStats.totalReviews }}</p>
|
<p class="text-4xl sm:text-5xl font-black bg-gradient-to-b from-brand-400 to-brand-600 bg-clip-text text-transparent">{{ testimonialsStats.totalReviews }}</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('testimonials.stats.clients') }}</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-2 font-medium">{{ t('testimonials.stats.clients') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-px bg-gray-200 dark:bg-gray-800" />
|
<div class="w-px bg-gradient-to-b from-transparent via-gray-300 dark:via-gray-700 to-transparent" />
|
||||||
<div class="text-center">
|
<div class="text-center group">
|
||||||
<p class="text-4xl font-extrabold text-brand-500 dark:text-brand-400">{{ testimonialsStats.averageRating }}/5</p>
|
<p class="text-4xl sm:text-5xl font-black bg-gradient-to-b from-brand-400 to-brand-600 bg-clip-text text-transparent">{{ testimonialsStats.averageRating }}/5</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('testimonials.stats.rating') }}</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-2 font-medium">{{ t('testimonials.stats.rating') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-px bg-gray-200 dark:bg-gray-800" />
|
<div class="w-px bg-gradient-to-b from-transparent via-gray-300 dark:via-gray-700 to-transparent" />
|
||||||
<div class="text-center">
|
<div class="text-center group">
|
||||||
<p class="text-4xl font-extrabold text-brand-500 dark:text-brand-400">{{ testimonialsStats.projectsCompleted }}</p>
|
<p class="text-4xl sm:text-5xl font-black bg-gradient-to-b from-brand-400 to-brand-600 bg-clip-text text-transparent">{{ testimonialsStats.projectsCompleted }}</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('testimonials.stats.projects') }}</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-2 font-medium">{{ t('testimonials.stats.projects') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Horizontal scrolling testimonials -->
|
<!-- Horizontal scrolling testimonials -->
|
||||||
<div class="flex gap-6 overflow-x-auto pb-4 -mx-4 px-4 snap-x snap-mandatory scrollbar-hide">
|
<div class="flex gap-5 overflow-x-auto overflow-y-visible pb-8 -mx-4 px-4 pt-2 snap-x snap-mandatory scrollbar-hide">
|
||||||
<div
|
<div
|
||||||
v-for="(testimonial, index) in testimonials"
|
v-for="(testimonial, index) in testimonials"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="flex-none w-[340px] sm:w-[380px] snap-start"
|
class="flex-none w-[340px] sm:w-[400px] snap-start"
|
||||||
>
|
>
|
||||||
<div class="h-full rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 flex flex-col gap-4 transition-all duration-300 hover:border-brand-500/30 hover:shadow-lg hover:shadow-brand-500/5">
|
<div class="h-full relative rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-7 flex flex-col gap-5 transition-all duration-300 hover:border-brand-500/40 hover:shadow-xl hover:shadow-brand-500/10 hover:-translate-y-1">
|
||||||
|
<!-- Decorative quote mark -->
|
||||||
|
<div class="absolute top-5 right-6 text-6xl font-serif text-brand-500/10 dark:text-brand-400/10 leading-none select-none pointer-events-none" aria-hidden="true">"</div>
|
||||||
|
|
||||||
<!-- Rating stars -->
|
<!-- Rating stars -->
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<UIcon
|
<UIcon
|
||||||
@@ -51,23 +54,23 @@ const { t } = useI18n()
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Quote -->
|
<!-- Quote -->
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-300 leading-relaxed flex-1 italic">
|
<p class="text-sm text-gray-600 dark:text-gray-300 leading-relaxed flex-1 relative z-10">
|
||||||
"{{ testimonial.content }}"
|
"{{ testimonial.content }}"
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Author -->
|
<!-- Author -->
|
||||||
<div class="flex items-center gap-3 pt-2 border-t border-gray-100 dark:border-gray-800">
|
<div class="flex items-center gap-3 pt-4 border-t border-gray-100 dark:border-gray-800/60">
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
:src="testimonial.avatar"
|
:src="testimonial.avatar"
|
||||||
:alt="testimonial.name"
|
:alt="testimonial.name"
|
||||||
width="40"
|
width="40"
|
||||||
height="40"
|
height="40"
|
||||||
class="rounded-full ring-2 ring-gray-100 dark:ring-gray-800"
|
class="rounded-full ring-2 ring-brand-500/20 dark:ring-brand-400/20"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p class="font-semibold text-sm text-gray-900 dark:text-white">{{ testimonial.name }}</p>
|
<p class="font-semibold text-sm text-gray-900 dark:text-white">{{ testimonial.name }}</p>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ testimonial.project_type }} - {{ testimonial.platform }}</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 font-mono">{{ testimonial.project_type }} - {{ testimonial.platform }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-3
@@ -4,7 +4,7 @@ export type { SiteConfig, ContactInfo, SocialLink, FiverrService, FiverrConfig }
|
|||||||
|
|
||||||
export const siteConfig: SiteConfig = {
|
export const siteConfig: SiteConfig = {
|
||||||
name: 'Killian',
|
name: 'Killian',
|
||||||
title: 'Killian - Full Stack Developer | Vue.js, React, Node.js Expert',
|
title: "Killian' DAL-CIN - Full Stack Developer | Vue.js, React, Node.js Expert",
|
||||||
description:
|
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.',
|
'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',
|
||||||
@@ -12,7 +12,6 @@ export const siteConfig: SiteConfig = {
|
|||||||
|
|
||||||
contact: {
|
contact: {
|
||||||
email: 'contact@killiandalcin.fr',
|
email: 'contact@killiandalcin.fr',
|
||||||
phone: '+33 6 49 19 38 16',
|
|
||||||
location: 'France',
|
location: 'France',
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -92,7 +91,7 @@ export const siteConfig: SiteConfig = {
|
|||||||
},
|
},
|
||||||
organization: {
|
organization: {
|
||||||
'@type': 'ProfessionalService',
|
'@type': 'ProfessionalService',
|
||||||
name: 'Killian Dalcin - Developpeur Full Stack',
|
name: "Killian' DAL-CIN - Developpeur Full Stack",
|
||||||
logo: 'https://killiandalcin.fr/logo.webp',
|
logo: 'https://killiandalcin.fr/logo.webp',
|
||||||
priceRange: '$$$',
|
priceRange: '$$$',
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
|
|||||||
+13
-10
@@ -10,29 +10,32 @@ function handleError() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen flex flex-col items-center justify-center gap-8 px-4 bg-white dark:bg-gray-950">
|
<div class="min-h-screen flex flex-col items-center justify-center gap-8 px-4 bg-white dark:bg-gray-950 relative overflow-hidden">
|
||||||
<!-- Decorative background -->
|
<!-- Decorative background -->
|
||||||
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
<div class="absolute inset-0 pointer-events-none" aria-hidden="true">
|
||||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[400px] bg-brand-500/5 dark:bg-brand-500/10 rounded-full blur-3xl" />
|
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[400px] bg-brand-500/5 dark:bg-brand-500/10 rounded-full blur-3xl" />
|
||||||
|
<!-- Dot grid -->
|
||||||
|
<div class="absolute inset-0 opacity-[0.03] dark:opacity-[0.05]" style="background-image: radial-gradient(circle, currentColor 1px, transparent 1px); background-size: 32px 32px;" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative z-10 text-center space-y-6 max-w-lg">
|
<div class="relative z-10 text-center space-y-8 max-w-lg">
|
||||||
<!-- Error code with glitch-like styling -->
|
<!-- Error code -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<h1 class="text-[10rem] sm:text-[12rem] font-black leading-none tracking-tighter bg-gradient-to-b from-brand-500 to-brand-600 bg-clip-text text-transparent select-none">
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// error</span>
|
||||||
|
<h1 class="text-[10rem] sm:text-[12rem] font-black leading-none tracking-tighter bg-gradient-to-b from-brand-400 via-brand-500 to-brand-700 bg-clip-text text-transparent select-none mt-2">
|
||||||
{{ error.statusCode }}
|
{{ error.statusCode }}
|
||||||
</h1>
|
</h1>
|
||||||
<!-- Shadow text behind -->
|
<!-- Shadow glow behind -->
|
||||||
<span class="absolute inset-0 text-[10rem] sm:text-[12rem] font-black leading-none tracking-tighter text-brand-500/10 blur-sm select-none" aria-hidden="true">
|
<span class="absolute inset-0 top-8 text-[10rem] sm:text-[12rem] font-black leading-none tracking-tighter text-brand-500/8 blur-md select-none" aria-hidden="true">
|
||||||
{{ error.statusCode }}
|
{{ error.statusCode }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<p class="text-xl sm:text-2xl font-bold text-gray-900 dark:text-white">
|
<p class="text-xl sm:text-2xl font-bold bg-gradient-to-r from-gray-900 to-gray-600 dark:from-white dark:to-gray-400 bg-clip-text text-transparent">
|
||||||
{{ error.statusCode === 404 ? t('error.notFound') : t('error.generic') }}
|
{{ error.statusCode === 404 ? t('error.notFound') : t('error.generic') }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-500 dark:text-gray-400">
|
<p class="text-gray-500 dark:text-gray-400 leading-relaxed">
|
||||||
{{ error.statusCode === 404
|
{{ error.statusCode === 404
|
||||||
? 'The page you are looking for does not exist or has been moved.'
|
? 'The page you are looking for does not exist or has been moved.'
|
||||||
: 'Something unexpected happened. Please try again.'
|
: 'Something unexpected happened. Please try again.'
|
||||||
@@ -40,7 +43,7 @@ function handleError() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UButton size="xl" icon="i-lucide-home" class="font-semibold" @click="handleError">
|
<UButton size="xl" icon="i-lucide-home" class="font-semibold" trailing-icon="i-lucide-arrow-right" @click="handleError">
|
||||||
{{ t('error.backHome') }}
|
{{ t('error.backHome') }}
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen flex flex-col bg-white dark:bg-gray-950 text-gray-900 dark:text-gray-100">
|
<div class="min-h-screen flex flex-col bg-white dark:bg-gray-950 text-gray-900 dark:text-gray-100 antialiased">
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<main class="flex-1">
|
<main class="flex-1">
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
+47
-51
@@ -65,42 +65,45 @@ const approachCards = computed(() => [
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="pt-16 pb-20 px-4 bg-gray-50 dark:bg-gray-900/30">
|
<section class="relative pt-20 pb-20 px-4 sm:px-6 lg:px-8 overflow-hidden">
|
||||||
<div class="max-w-4xl mx-auto text-center">
|
<div class="absolute inset-0 bg-gray-50/80 dark:bg-gray-900/40" aria-hidden="true" />
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">About</span>
|
<div class="absolute top-0 right-0 w-[600px] h-[600px] bg-brand-500/5 dark:bg-brand-500/8 rounded-full blur-3xl -translate-y-1/2 translate-x-1/4 pointer-events-none" aria-hidden="true" />
|
||||||
<h1 class="text-4xl sm:text-5xl font-bold mt-2 mb-6 text-gray-900 dark:text-white">
|
|
||||||
|
<div class="relative z-10 max-w-4xl mx-auto text-center">
|
||||||
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// about</span>
|
||||||
|
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold mt-3 mb-6 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">
|
||||||
{{ t('about.title') }}
|
{{ t('about.title') }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-xl text-gray-500 dark:text-gray-400 mb-10 max-w-2xl mx-auto">
|
<p class="text-xl text-gray-500 dark:text-gray-400 mb-12 max-w-2xl mx-auto leading-relaxed">
|
||||||
{{ t('about.subtitle') }}
|
{{ t('about.subtitle') }}
|
||||||
</p>
|
</p>
|
||||||
<div class="space-y-4 text-lg text-gray-600 dark:text-gray-400 max-w-3xl mx-auto leading-relaxed">
|
<div class="max-w-3xl mx-auto space-y-6">
|
||||||
<p>{{ t('about.intro.content') }}</p>
|
<p class="text-lg text-gray-600 dark:text-gray-400 leading-relaxed rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/60 dark:bg-gray-900/40 backdrop-blur-sm p-6 sm:p-8 text-left">{{ t('about.intro.content') }}</p>
|
||||||
<p>{{ t('about.experience.content') }}</p>
|
<p class="text-lg text-gray-600 dark:text-gray-400 leading-relaxed rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/60 dark:bg-gray-900/40 backdrop-blur-sm p-6 sm:p-8 text-left">{{ t('about.experience.content') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Skills Section -->
|
<!-- Skills Section -->
|
||||||
<section class="py-20 md:py-28 px-4">
|
<section class="py-24 md:py-32 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="max-w-7xl mx-auto">
|
||||||
<div class="text-center mb-14">
|
<div class="text-center mb-16">
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Tech Stack</span>
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// tech-stack</span>
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('about.skills.title') }}</h2>
|
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold mt-3 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">{{ t('about.skills.title') }}</h2>
|
||||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3 max-w-2xl mx-auto">
|
<p class="text-lg text-gray-500 dark:text-gray-400 mt-4 max-w-2xl mx-auto leading-relaxed">
|
||||||
{{ t('about.subtitle') }}
|
{{ t('about.subtitle') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tech Categories Grid -->
|
<!-- Tech Categories Bento Grid -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5 mb-5">
|
||||||
<div
|
<div
|
||||||
v-for="category in techCategories"
|
v-for="category in techCategories"
|
||||||
:key="category.key"
|
:key="category.key"
|
||||||
class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8"
|
class="group rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-7 sm:p-8 transition-all duration-300 hover:border-brand-500/30 hover:shadow-lg hover:shadow-brand-500/5"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3 mb-6">
|
<div class="flex items-center gap-3 mb-6">
|
||||||
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center">
|
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center transition-all duration-300 group-hover:bg-brand-500/20 group-hover:scale-110">
|
||||||
<UIcon :name="category.icon" class="text-xl text-brand-600 dark:text-brand-400" />
|
<UIcon :name="category.icon" class="text-xl text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">{{ category.title }}</h3>
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white">{{ category.title }}</h3>
|
||||||
@@ -117,9 +120,9 @@ const approachCards = computed(() => [
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Operating Systems -->
|
<!-- Operating Systems -->
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8">
|
<div class="group rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-7 sm:p-8 transition-all duration-300 hover:border-brand-500/30 hover:shadow-lg hover:shadow-brand-500/5">
|
||||||
<div class="flex items-center gap-3 mb-6">
|
<div class="flex items-center gap-3 mb-6">
|
||||||
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center">
|
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center transition-all duration-300 group-hover:bg-brand-500/20 group-hover:scale-110">
|
||||||
<UIcon name="i-lucide-monitor" class="text-xl text-brand-600 dark:text-brand-400" />
|
<UIcon name="i-lucide-monitor" class="text-xl text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">{{ t('about.skills.systems') }}</h3>
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white">{{ t('about.skills.systems') }}</h3>
|
||||||
@@ -137,28 +140,34 @@ const approachCards = computed(() => [
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Approach Section -->
|
<!-- Approach Section -->
|
||||||
<section class="py-20 md:py-28 px-4 bg-gray-50 dark:bg-gray-900/30">
|
<section class="relative py-24 md:py-32 px-4 sm:px-6 lg:px-8 overflow-hidden">
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="absolute inset-0 bg-gray-50/80 dark:bg-gray-900/40" aria-hidden="true" />
|
||||||
<div class="text-center mb-14">
|
<div class="absolute bottom-0 left-0 w-[500px] h-[500px] bg-brand-500/5 dark:bg-brand-500/8 rounded-full blur-3xl translate-y-1/2 -translate-x-1/4 pointer-events-none" aria-hidden="true" />
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Methodology</span>
|
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('about.approach.title') }}</h2>
|
<div class="relative z-10 max-w-7xl mx-auto">
|
||||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3 max-w-2xl mx-auto">
|
<div class="text-center mb-16">
|
||||||
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// methodology</span>
|
||||||
|
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold mt-3 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">{{ t('about.approach.title') }}</h2>
|
||||||
|
<p class="text-lg text-gray-500 dark:text-gray-400 mt-4 max-w-2xl mx-auto leading-relaxed">
|
||||||
{{ t('about.approach.subtitle') }}
|
{{ t('about.approach.subtitle') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||||
<div
|
<div
|
||||||
v-for="(card, index) in approachCards"
|
v-for="(card, index) in approachCards"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="group rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8 transition-all duration-300 hover:border-brand-500/30 hover:shadow-lg hover:shadow-brand-500/5"
|
class="group rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-7 sm:p-8 transition-all duration-300 hover:border-brand-500/40 hover:shadow-xl hover:shadow-brand-500/10 hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
<div class="flex items-start gap-4">
|
<!-- Hover glow -->
|
||||||
<div class="w-12 h-12 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0 transition-colors group-hover:bg-brand-500/20">
|
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-brand-500/0 to-emerald-500/0 group-hover:from-brand-500/5 group-hover:to-emerald-500/5 transition-all duration-500 pointer-events-none" aria-hidden="true" />
|
||||||
|
|
||||||
|
<div class="relative z-10 flex items-start gap-4">
|
||||||
|
<div class="w-12 h-12 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0 transition-all duration-300 group-hover:bg-brand-500/20 group-hover:scale-110">
|
||||||
<UIcon :name="card.icon" class="text-xl text-brand-600 dark:text-brand-400" />
|
<UIcon :name="card.icon" class="text-xl text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ card.title }}</h3>
|
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2 group-hover:text-brand-600 dark:group-hover:text-brand-400 transition-colors">{{ card.title }}</h3>
|
||||||
<p class="text-gray-500 dark:text-gray-400 leading-relaxed">{{ card.description }}</p>
|
<p class="text-gray-500 dark:text-gray-400 leading-relaxed">{{ card.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,26 +177,13 @@ const approachCards = computed(() => [
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- CTA Section -->
|
<!-- CTA Section -->
|
||||||
<section class="py-20 md:py-28 px-4">
|
<CTASection
|
||||||
<div class="max-w-5xl mx-auto">
|
:title="t('about.cta.title')"
|
||||||
<div class="relative overflow-hidden rounded-3xl bg-gradient-to-br from-brand-600 via-brand-500 to-emerald-500 px-8 py-16 sm:px-16 sm:py-20 text-center">
|
:subtitle="t('about.cta.description')"
|
||||||
<div class="absolute top-0 left-0 w-72 h-72 bg-white/10 rounded-full -translate-x-1/2 -translate-y-1/2 blur-2xl" aria-hidden="true" />
|
:primary-text="t('about.cta.button')"
|
||||||
<div class="absolute bottom-0 right-0 w-96 h-96 bg-black/10 rounded-full translate-x-1/3 translate-y-1/3 blur-2xl" aria-hidden="true" />
|
primary-to="/contact"
|
||||||
|
:secondary-text="t('home.cta.viewProjects')"
|
||||||
<div class="relative z-10">
|
secondary-to="/projects"
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">{{ t('about.cta.title') }}</h2>
|
/>
|
||||||
<p class="text-lg text-white/80 mb-10 max-w-2xl mx-auto">{{ t('about.cta.description') }}</p>
|
|
||||||
<div class="flex flex-wrap justify-center gap-4">
|
|
||||||
<UButton :to="localePath('/contact')" size="xl" color="white" class="font-semibold">
|
|
||||||
{{ t('about.cta.button') }}
|
|
||||||
</UButton>
|
|
||||||
<UButton :to="localePath('/projects')" size="xl" variant="outline" color="white" class="font-semibold">
|
|
||||||
{{ t('home.cta.viewProjects') }}
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+62
-57
@@ -18,43 +18,49 @@ useSeoMeta({
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="pt-16 pb-12 px-4 bg-gray-50 dark:bg-gray-900/30">
|
<section class="relative pt-20 pb-16 px-4 sm:px-6 lg:px-8 overflow-hidden">
|
||||||
<div class="max-w-4xl mx-auto text-center">
|
<div class="absolute inset-0 bg-gray-50/80 dark:bg-gray-900/40" aria-hidden="true" />
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Contact</span>
|
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[400px] bg-brand-500/5 dark:bg-brand-500/8 rounded-full blur-3xl pointer-events-none" aria-hidden="true" />
|
||||||
<h1 class="text-4xl sm:text-5xl font-bold mt-2 mb-6 text-gray-900 dark:text-white">
|
|
||||||
|
<div class="relative z-10 max-w-4xl mx-auto text-center">
|
||||||
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// contact</span>
|
||||||
|
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold mt-3 mb-6 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">
|
||||||
{{ t('contact.title') }}
|
{{ t('contact.title') }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-xl text-gray-500 dark:text-gray-400 mb-10 max-w-2xl mx-auto">
|
<p class="text-xl text-gray-500 dark:text-gray-400 mb-12 max-w-2xl mx-auto leading-relaxed">
|
||||||
{{ t('contact.subtitle') }}
|
{{ t('contact.subtitle') }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<div class="flex flex-wrap justify-center gap-10">
|
<div class="flex flex-wrap justify-center gap-8 sm:gap-12">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">24-48h</div>
|
<div class="text-3xl sm:text-4xl font-black bg-gradient-to-b from-brand-400 to-brand-600 bg-clip-text text-transparent">24-48h</div>
|
||||||
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('contact.stats.responseTime') }}</div>
|
<div class="text-sm text-gray-500 dark:text-gray-400 mt-2 font-medium">{{ t('contact.stats.responseTime') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-px bg-gray-200 dark:bg-gray-800 hidden sm:block" />
|
<div class="w-px bg-gradient-to-b from-transparent via-gray-300 dark:via-gray-700 to-transparent hidden sm:block" />
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">100%</div>
|
<div class="text-3xl sm:text-4xl font-black bg-gradient-to-b from-brand-400 to-brand-600 bg-clip-text text-transparent">100%</div>
|
||||||
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('contact.stats.satisfaction') }}</div>
|
<div class="text-sm text-gray-500 dark:text-gray-400 mt-2 font-medium">{{ t('contact.stats.satisfaction') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-px bg-gray-200 dark:bg-gray-800 hidden sm:block" />
|
<div class="w-px bg-gradient-to-b from-transparent via-gray-300 dark:via-gray-700 to-transparent hidden sm:block" />
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">Remote</div>
|
<div class="text-3xl sm:text-4xl font-black bg-gradient-to-b from-brand-400 to-brand-600 bg-clip-text text-transparent">Remote</div>
|
||||||
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('contact.stats.collaboration') }}</div>
|
<div class="text-sm text-gray-500 dark:text-gray-400 mt-2 font-medium">{{ t('contact.stats.collaboration') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Two Column Layout -->
|
<!-- Two Column Layout -->
|
||||||
<section class="py-16 md:py-20 px-4">
|
<section class="py-16 md:py-24 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-5 gap-10 lg:gap-16">
|
<div class="max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-5 gap-8 lg:gap-12">
|
||||||
<!-- Left: Contact Form (wider) -->
|
<!-- Left: Contact Form (wider) -->
|
||||||
<div class="lg:col-span-3">
|
<div class="lg:col-span-3">
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8">
|
<div class="rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-7 sm:p-8">
|
||||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">{{ t('contact.form.title') }}</h2>
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6 flex items-center gap-3">
|
||||||
|
<div class="w-1 h-6 rounded-full bg-brand-500" />
|
||||||
|
{{ t('contact.form.title') }}
|
||||||
|
</h2>
|
||||||
<ContactForm />
|
<ContactForm />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,53 +68,50 @@ useSeoMeta({
|
|||||||
<!-- Right: Contact Info + Social -->
|
<!-- Right: Contact Info + Social -->
|
||||||
<div class="lg:col-span-2 flex flex-col gap-6">
|
<div class="lg:col-span-2 flex flex-col gap-6">
|
||||||
<!-- Contact Info -->
|
<!-- Contact Info -->
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8">
|
<div class="rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-7 sm:p-8">
|
||||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-6">{{ t('contact.quickContact') }}</h2>
|
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-6 flex items-center gap-3">
|
||||||
<div class="flex flex-col gap-5">
|
<div class="w-1 h-5 rounded-full bg-brand-500" />
|
||||||
|
{{ t('contact.quickContact') }}
|
||||||
|
</h2>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
<a
|
<a
|
||||||
:href="`mailto:${siteConfig.contact.email}`"
|
:href="`mailto:${siteConfig.contact.email}`"
|
||||||
class="flex items-center gap-4 group"
|
class="flex items-center gap-4 p-3 rounded-xl border border-transparent hover:border-brand-500/20 hover:bg-brand-500/5 transition-all duration-200 group"
|
||||||
>
|
>
|
||||||
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0 transition-colors group-hover:bg-brand-500/20">
|
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0 transition-all duration-300 group-hover:bg-brand-500/20 group-hover:scale-110">
|
||||||
<UIcon name="i-lucide-mail" class="text-brand-600 dark:text-brand-400" />
|
<UIcon name="i-lucide-mail" class="text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-600 dark:text-gray-300 group-hover:text-brand-500 transition-colors">{{ siteConfig.contact.email }}</span>
|
<span class="text-gray-600 dark:text-gray-300 group-hover:text-brand-500 transition-colors font-medium">{{ siteConfig.contact.email }}</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<div class="flex items-center gap-4 p-3">
|
||||||
:href="`tel:${siteConfig.contact.phone.replace(/\s/g, '')}`"
|
|
||||||
class="flex items-center gap-4 group"
|
|
||||||
>
|
|
||||||
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0 transition-colors group-hover:bg-brand-500/20">
|
|
||||||
<UIcon name="i-lucide-phone" class="text-brand-600 dark:text-brand-400" />
|
|
||||||
</div>
|
|
||||||
<span class="text-gray-600 dark:text-gray-300 group-hover:text-brand-500 transition-colors">{{ siteConfig.contact.phone }}</span>
|
|
||||||
</a>
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0">
|
<div class="w-10 h-10 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0">
|
||||||
<UIcon name="i-lucide-map-pin" class="text-brand-600 dark:text-brand-400" />
|
<UIcon name="i-lucide-map-pin" class="text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-600 dark:text-gray-300">{{ siteConfig.contact.location }}</span>
|
<span class="text-gray-600 dark:text-gray-300 font-medium">{{ siteConfig.contact.location }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Social Links -->
|
<!-- Social Links -->
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 sm:p-8">
|
<div class="rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-7 sm:p-8">
|
||||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-6">{{ t('contact.findMeOn') }}</h2>
|
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-6 flex items-center gap-3">
|
||||||
<div class="flex flex-col gap-3">
|
<div class="w-1 h-5 rounded-full bg-brand-500" />
|
||||||
|
{{ t('contact.findMeOn') }}
|
||||||
|
</h2>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
<a
|
<a
|
||||||
v-for="social in siteConfig.social.filter(s => s.icon !== 'i-lucide-mail')"
|
v-for="social in siteConfig.social.filter(s => s.icon !== 'i-lucide-mail')"
|
||||||
:key="social.name"
|
:key="social.name"
|
||||||
:href="social.url"
|
:href="social.url"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="flex items-center gap-4 p-3 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors group"
|
class="flex items-center gap-4 p-3 rounded-xl border border-transparent hover:border-brand-500/20 hover:bg-brand-500/5 transition-all duration-200 group"
|
||||||
>
|
>
|
||||||
<div class="w-10 h-10 rounded-xl bg-gray-100 dark:bg-gray-800 flex items-center justify-center shrink-0 group-hover:bg-brand-500/10 transition-colors">
|
<div class="w-10 h-10 rounded-xl bg-gray-100 dark:bg-gray-800/80 border border-gray-200/50 dark:border-gray-700/30 flex items-center justify-center shrink-0 group-hover:bg-brand-500/10 group-hover:border-brand-500/20 transition-all duration-300">
|
||||||
<UIcon :name="social.icon" class="text-gray-500 dark:text-gray-400 group-hover:text-brand-500 transition-colors" />
|
<UIcon :name="social.icon" class="text-gray-500 dark:text-gray-400 group-hover:text-brand-500 transition-colors" />
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-700 dark:text-gray-300 font-medium group-hover:text-brand-500 transition-colors">{{ social.name }}</span>
|
<span class="text-gray-700 dark:text-gray-300 font-medium group-hover:text-brand-500 transition-colors">{{ social.name }}</span>
|
||||||
<UIcon name="i-lucide-external-link" class="text-xs text-gray-400 dark:text-gray-600 ml-auto" />
|
<UIcon name="i-lucide-external-link" class="text-xs text-gray-400 dark:text-gray-600 ml-auto group-hover:text-brand-400 transition-colors" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,38 +120,40 @@ useSeoMeta({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- FAQ Info Cards -->
|
<!-- FAQ Info Cards -->
|
||||||
<section class="py-16 md:py-20 px-4 bg-gray-50 dark:bg-gray-900/30">
|
<section class="relative py-20 md:py-28 px-4 sm:px-6 lg:px-8 overflow-hidden">
|
||||||
<div class="max-w-6xl mx-auto">
|
<div class="absolute inset-0 bg-gray-50/80 dark:bg-gray-900/40" aria-hidden="true" />
|
||||||
<div class="text-center mb-14">
|
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Info</span>
|
<div class="relative z-10 max-w-6xl mx-auto">
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('contact.faq.title') }}</h2>
|
<div class="text-center mb-16">
|
||||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3 max-w-2xl mx-auto">
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// info</span>
|
||||||
|
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold mt-3 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">{{ t('contact.faq.title') }}</h2>
|
||||||
|
<p class="text-lg text-gray-500 dark:text-gray-400 mt-4 max-w-2xl mx-auto leading-relaxed">
|
||||||
{{ t('contact.faq.subtitle') }}
|
{{ t('contact.faq.subtitle') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-5">
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-8 text-center group hover:border-brand-500/30 transition-all duration-300">
|
<div class="group rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-8 text-center transition-all duration-300 hover:border-brand-500/40 hover:shadow-xl hover:shadow-brand-500/10 hover:-translate-y-1">
|
||||||
<div class="w-14 h-14 rounded-2xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mx-auto mb-5 group-hover:bg-brand-500/20 transition-colors">
|
<div class="w-14 h-14 rounded-2xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mx-auto mb-5 transition-all duration-300 group-hover:bg-brand-500/20 group-hover:scale-110">
|
||||||
<UIcon name="i-lucide-clock" class="text-2xl text-brand-600 dark:text-brand-400" />
|
<UIcon name="i-lucide-clock" class="text-2xl text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ t('contact.faq.responseTime.title') }}</h3>
|
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-3 group-hover:text-brand-600 dark:group-hover:text-brand-400 transition-colors">{{ t('contact.faq.responseTime.title') }}</h3>
|
||||||
<p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">{{ t('contact.faq.responseTime.description') }}</p>
|
<p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">{{ t('contact.faq.responseTime.description') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-8 text-center group hover:border-brand-500/30 transition-all duration-300">
|
<div class="group rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-8 text-center transition-all duration-300 hover:border-brand-500/40 hover:shadow-xl hover:shadow-brand-500/10 hover:-translate-y-1">
|
||||||
<div class="w-14 h-14 rounded-2xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mx-auto mb-5 group-hover:bg-brand-500/20 transition-colors">
|
<div class="w-14 h-14 rounded-2xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mx-auto mb-5 transition-all duration-300 group-hover:bg-brand-500/20 group-hover:scale-110">
|
||||||
<UIcon name="i-lucide-building" class="text-2xl text-brand-600 dark:text-brand-400" />
|
<UIcon name="i-lucide-building" class="text-2xl text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ t('contact.faq.projectTypes.title') }}</h3>
|
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-3 group-hover:text-brand-600 dark:group-hover:text-brand-400 transition-colors">{{ t('contact.faq.projectTypes.title') }}</h3>
|
||||||
<p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">{{ t('contact.faq.projectTypes.description') }}</p>
|
<p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">{{ t('contact.faq.projectTypes.description') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-8 text-center group hover:border-brand-500/30 transition-all duration-300">
|
<div class="group rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm p-8 text-center transition-all duration-300 hover:border-brand-500/40 hover:shadow-xl hover:shadow-brand-500/10 hover:-translate-y-1">
|
||||||
<div class="w-14 h-14 rounded-2xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mx-auto mb-5 group-hover:bg-brand-500/20 transition-colors">
|
<div class="w-14 h-14 rounded-2xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center mx-auto mb-5 transition-all duration-300 group-hover:bg-brand-500/20 group-hover:scale-110">
|
||||||
<UIcon name="i-lucide-users" class="text-2xl text-brand-600 dark:text-brand-400" />
|
<UIcon name="i-lucide-users" class="text-2xl text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ t('contact.faq.collaboration.title') }}</h3>
|
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-3 group-hover:text-brand-600 dark:group-hover:text-brand-400 transition-colors">{{ t('contact.faq.collaboration.title') }}</h3>
|
||||||
<p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">{{ t('contact.faq.collaboration.description') }}</p>
|
<p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">{{ t('contact.faq.collaboration.description') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+34
-46
@@ -33,21 +33,24 @@ const heroStats = computed(() => [
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="pt-16 pb-16 px-4 bg-gray-50 dark:bg-gray-900/30">
|
<section class="relative pt-20 pb-16 px-4 sm:px-6 lg:px-8 overflow-hidden">
|
||||||
<div class="max-w-4xl mx-auto text-center">
|
<div class="absolute inset-0 bg-gray-50/80 dark:bg-gray-900/40" aria-hidden="true" />
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Fiverr</span>
|
<div class="absolute top-0 right-0 w-[600px] h-[600px] bg-brand-500/5 dark:bg-brand-500/8 rounded-full blur-3xl -translate-y-1/2 translate-x-1/4 pointer-events-none" aria-hidden="true" />
|
||||||
<h1 class="text-4xl sm:text-5xl font-bold mt-2 mb-6 text-gray-900 dark:text-white">
|
|
||||||
|
<div class="relative z-10 max-w-4xl mx-auto text-center">
|
||||||
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// fiverr</span>
|
||||||
|
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold mt-3 mb-6 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">
|
||||||
{{ t('fiverr.title') }}
|
{{ t('fiverr.title') }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-xl text-gray-500 dark:text-gray-400 mb-10 max-w-2xl mx-auto">
|
<p class="text-xl text-gray-500 dark:text-gray-400 mb-12 max-w-2xl mx-auto leading-relaxed">
|
||||||
{{ t('fiverr.subtitle') }}
|
{{ t('fiverr.subtitle') }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<div class="flex flex-wrap justify-center gap-10 mb-10">
|
<div class="flex flex-wrap justify-center gap-8 sm:gap-12 mb-12">
|
||||||
<div v-for="stat in heroStats" :key="stat.label" class="text-center">
|
<div v-for="stat in heroStats" :key="stat.label" class="text-center">
|
||||||
<div class="text-4xl font-extrabold text-brand-500 dark:text-brand-400">{{ stat.number }}</div>
|
<div class="text-4xl sm:text-5xl font-black bg-gradient-to-b from-brand-400 to-brand-600 bg-clip-text text-transparent">{{ stat.number }}</div>
|
||||||
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ stat.label }}</div>
|
<div class="text-sm text-gray-500 dark:text-gray-400 mt-2 font-medium">{{ stat.label }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -65,19 +68,19 @@ const heroStats = computed(() => [
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Services Section -->
|
<!-- Services Section -->
|
||||||
<section class="py-20 md:py-28 px-4">
|
<section class="py-24 md:py-32 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="max-w-7xl mx-auto">
|
||||||
<div class="text-center mb-14">
|
<div class="text-center mb-16">
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Services</span>
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// services</span>
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold mt-2 text-gray-900 dark:text-white">{{ t('fiverr.services.title') }}</h2>
|
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold mt-3 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">{{ t('fiverr.services.title') }}</h2>
|
||||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-3">{{ t('fiverr.services.subtitle') }}</p>
|
<p class="text-lg text-gray-500 dark:text-gray-400 mt-4 leading-relaxed">{{ t('fiverr.services.subtitle') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-5 lg:gap-6">
|
||||||
<div
|
<div
|
||||||
v-for="service in services"
|
v-for="service in services"
|
||||||
:key="service.id"
|
:key="service.id"
|
||||||
class="group rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 overflow-hidden transition-all duration-300 hover:border-brand-500/30 hover:shadow-xl hover:shadow-brand-500/5 hover:-translate-y-1"
|
class="group rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/80 dark:bg-gray-900/60 backdrop-blur-sm overflow-hidden transition-all duration-300 hover:border-brand-500/40 hover:shadow-xl hover:shadow-brand-500/10 hover:-translate-y-1.5"
|
||||||
>
|
>
|
||||||
<!-- Service Image -->
|
<!-- Service Image -->
|
||||||
<div class="relative overflow-hidden">
|
<div class="relative overflow-hidden">
|
||||||
@@ -87,10 +90,10 @@ const heroStats = computed(() => [
|
|||||||
class="w-full h-52 object-cover transition-transform duration-500 group-hover:scale-105"
|
class="w-full h-52 object-cover transition-transform duration-500 group-hover:scale-105"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
<div class="absolute inset-0 bg-gradient-to-t from-black/40 via-transparent to-transparent" />
|
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent" />
|
||||||
<!-- Price badge overlay -->
|
<!-- Price badge overlay -->
|
||||||
<div class="absolute bottom-3 left-3">
|
<div class="absolute bottom-3 left-3">
|
||||||
<span class="px-3 py-1.5 rounded-lg bg-brand-500 text-white text-sm font-bold shadow-lg">
|
<span class="px-3 py-1.5 rounded-lg bg-brand-500 text-white text-sm font-bold shadow-lg backdrop-blur-sm">
|
||||||
{{ t('fiverr.pricing.startingAt') }} {{ service.price }}
|
{{ t('fiverr.pricing.startingAt') }} {{ service.price }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,8 +101,8 @@ const heroStats = computed(() => [
|
|||||||
<div class="absolute top-3 right-3">
|
<div class="absolute top-3 right-3">
|
||||||
<span
|
<span
|
||||||
:class="service.url !== '#'
|
:class="service.url !== '#'
|
||||||
? 'bg-green-500/90 text-white'
|
? 'bg-green-500/90 text-white backdrop-blur-sm'
|
||||||
: 'bg-yellow-500/90 text-white'"
|
: 'bg-yellow-500/90 text-white backdrop-blur-sm'"
|
||||||
class="px-2.5 py-1 rounded-lg text-xs font-semibold shadow-lg"
|
class="px-2.5 py-1 rounded-lg text-xs font-semibold shadow-lg"
|
||||||
>
|
>
|
||||||
{{ service.url !== '#' ? t('fiverr.services.available') : t('fiverr.services.comingSoon') }}
|
{{ service.url !== '#' ? t('fiverr.services.available') : t('fiverr.services.comingSoon') }}
|
||||||
@@ -108,11 +111,11 @@ const heroStats = computed(() => [
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="p-6">
|
<div class="p-6 sm:p-7">
|
||||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-2 group-hover:text-brand-500 transition-colors">
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3 group-hover:text-brand-600 dark:group-hover:text-brand-400 transition-colors">
|
||||||
{{ t(`fiverr.serviceData.${service.id}.title`) }}
|
{{ t(`fiverr.serviceData.${service.id}.title`) }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-500 dark:text-gray-400 mb-5 leading-relaxed">
|
<p class="text-gray-500 dark:text-gray-400 mb-6 leading-relaxed">
|
||||||
{{ t(`fiverr.serviceData.${service.id}.description`) }}
|
{{ t(`fiverr.serviceData.${service.id}.description`) }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -142,7 +145,7 @@ const heroStats = computed(() => [
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- FAQ Section -->
|
<!-- FAQ Section -->
|
||||||
<div class="bg-gray-50 dark:bg-gray-900/30">
|
<div class="relative bg-gray-50/50 dark:bg-gray-900/20">
|
||||||
<FAQSection
|
<FAQSection
|
||||||
:faqs="homeFAQs"
|
:faqs="homeFAQs"
|
||||||
:title="t('fiverr.faq.title')"
|
:title="t('fiverr.faq.title')"
|
||||||
@@ -151,29 +154,14 @@ const heroStats = computed(() => [
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CTA Section -->
|
<!-- CTA Section -->
|
||||||
<section class="py-20 md:py-28 px-4">
|
<CTASection
|
||||||
<div class="max-w-5xl mx-auto">
|
:title="t('fiverr.cta.title')"
|
||||||
<div class="relative overflow-hidden rounded-3xl bg-gradient-to-br from-brand-600 via-brand-500 to-emerald-500 px-8 py-16 sm:px-16 sm:py-20 text-center">
|
:subtitle="t('fiverr.cta.subtitle')"
|
||||||
<div class="absolute top-0 left-0 w-72 h-72 bg-white/10 rounded-full -translate-x-1/2 -translate-y-1/2 blur-2xl" aria-hidden="true" />
|
:primary-text="t('fiverr.cta.button')"
|
||||||
<div class="absolute bottom-0 right-0 w-96 h-96 bg-black/10 rounded-full translate-x-1/3 translate-y-1/3 blur-2xl" aria-hidden="true" />
|
:primary-to="siteConfig.fiverr.profileUrl"
|
||||||
|
:secondary-text="t('fiverr.profileCta')"
|
||||||
<div class="relative z-10">
|
secondary-to="/contact"
|
||||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">{{ t('fiverr.cta.title') }}</h2>
|
|
||||||
<p class="text-lg text-white/80 mb-10 max-w-2xl mx-auto">{{ t('fiverr.cta.subtitle') }}</p>
|
|
||||||
<UButton
|
|
||||||
:to="siteConfig.fiverr.profileUrl"
|
|
||||||
target="_blank"
|
|
||||||
external
|
external
|
||||||
size="xl"
|
/>
|
||||||
color="white"
|
|
||||||
trailing-icon="i-lucide-external-link"
|
|
||||||
class="font-semibold"
|
|
||||||
>
|
|
||||||
{{ t('fiverr.cta.button') }}
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+6
-10
@@ -23,7 +23,7 @@ useHead({
|
|||||||
'@graph': [
|
'@graph': [
|
||||||
{
|
{
|
||||||
'@type': 'Person',
|
'@type': 'Person',
|
||||||
name: 'Killian Dalcin',
|
name: "Killian' DAL-CIN",
|
||||||
url: 'https://killiandalcin.fr',
|
url: 'https://killiandalcin.fr',
|
||||||
jobTitle: 'Developpeur Full Stack Freelance',
|
jobTitle: 'Developpeur Full Stack Freelance',
|
||||||
email: 'contact@killiandalcin.fr',
|
email: 'contact@killiandalcin.fr',
|
||||||
@@ -35,14 +35,14 @@ useHead({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'ProfessionalService',
|
'@type': 'ProfessionalService',
|
||||||
name: 'Killian Dalcin - Developpeur Full Stack',
|
name: "Killian' DAL-CIN - Developpeur Full Stack",
|
||||||
url: 'https://killiandalcin.fr',
|
url: 'https://killiandalcin.fr',
|
||||||
logo: 'https://killiandalcin.fr/images/logo.webp',
|
logo: 'https://killiandalcin.fr/images/logo.webp',
|
||||||
priceRange: '$$$',
|
priceRange: '$$$',
|
||||||
areaServed: 'Worldwide',
|
areaServed: 'Worldwide',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -54,7 +54,7 @@ useHead({
|
|||||||
<HeroSection />
|
<HeroSection />
|
||||||
|
|
||||||
<!-- Featured Projects Section -->
|
<!-- Featured Projects Section -->
|
||||||
<div class="bg-gray-50 dark:bg-gray-900/30">
|
<div class="relative bg-gray-50/50 dark:bg-gray-900/20">
|
||||||
<FeaturedProjectsSection />
|
<FeaturedProjectsSection />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -62,16 +62,12 @@ useHead({
|
|||||||
<ServicesSection />
|
<ServicesSection />
|
||||||
|
|
||||||
<!-- Testimonials Section -->
|
<!-- Testimonials Section -->
|
||||||
<div class="bg-gray-50 dark:bg-gray-900/30">
|
<div class="relative bg-gray-50/50 dark:bg-gray-900/20">
|
||||||
<TestimonialsSection />
|
<TestimonialsSection />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- FAQ Section -->
|
<!-- FAQ Section -->
|
||||||
<FAQSection
|
<FAQSection :faqs="homeFAQs" :title="t('faq.title')" :subtitle="t('faq.subtitle')" />
|
||||||
:faqs="homeFAQs"
|
|
||||||
:title="t('faq.title')"
|
|
||||||
:subtitle="t('faq.subtitle')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- CTA Section -->
|
<!-- CTA Section -->
|
||||||
<CTASection />
|
<CTASection />
|
||||||
|
|||||||
+80
-62
@@ -30,49 +30,61 @@ useSeoMeta({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="project">
|
<div v-if="project">
|
||||||
<!-- Back navigation -->
|
<!-- Full-width hero image -->
|
||||||
<div class="bg-gray-50 dark:bg-gray-900/30 border-b border-gray-200 dark:border-gray-800">
|
<section class="relative overflow-hidden">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
<!-- Hero image with overlay -->
|
||||||
<UButton
|
<div class="relative h-[40vh] sm:h-[50vh] lg:h-[60vh]">
|
||||||
variant="ghost"
|
|
||||||
icon="i-lucide-arrow-left"
|
|
||||||
to="/projects"
|
|
||||||
size="sm"
|
|
||||||
class="text-gray-500 hover:text-gray-900 dark:hover:text-white"
|
|
||||||
>
|
|
||||||
{{ t('projects.projectDetail.backToProjects') }}
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Hero section -->
|
|
||||||
<section class="bg-gray-50 dark:bg-gray-900/30 pb-16 pt-8">
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-10 lg:gap-16 items-center">
|
|
||||||
<!-- Project Image -->
|
|
||||||
<div class="rounded-2xl overflow-hidden border border-gray-200 dark:border-gray-800 shadow-lg">
|
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
v-if="project.image"
|
v-if="project.image"
|
||||||
:src="project.image"
|
:src="project.image"
|
||||||
:alt="project.title"
|
:alt="project.title"
|
||||||
class="w-full h-auto object-cover"
|
class="w-full h-full object-cover"
|
||||||
format="webp"
|
format="webp"
|
||||||
loading="lazy"
|
loading="eager"
|
||||||
/>
|
/>
|
||||||
|
<!-- Gradient overlay -->
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-t from-white via-white/40 to-transparent dark:from-gray-950 dark:via-gray-950/40 dark:to-transparent" />
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-r from-white/60 to-transparent dark:from-gray-950/60 dark:to-transparent" />
|
||||||
|
|
||||||
|
<!-- Back button (floating) -->
|
||||||
|
<div class="absolute top-6 left-4 sm:left-6 lg:left-8 z-20">
|
||||||
|
<UButton
|
||||||
|
variant="solid"
|
||||||
|
color="neutral"
|
||||||
|
icon="i-lucide-arrow-left"
|
||||||
|
to="/projects"
|
||||||
|
size="sm"
|
||||||
|
class="shadow-lg backdrop-blur-sm"
|
||||||
|
>
|
||||||
|
{{ t('projects.projectDetail.backToProjects') }}
|
||||||
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Project Info -->
|
<!-- Title overlay at bottom -->
|
||||||
<div class="flex flex-col justify-center space-y-6">
|
<div class="absolute bottom-0 left-0 right-0 z-10 px-4 sm:px-6 lg:px-8 pb-10">
|
||||||
<div class="flex items-center gap-3">
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
<UBadge v-if="project.category" variant="subtle" size="md">{{ project.category }}</UBadge>
|
<UBadge v-if="project.category" variant="subtle" size="md">{{ project.category }}</UBadge>
|
||||||
<span v-if="project.date" class="text-sm text-gray-400 dark:text-gray-500 font-medium">{{ project.date }}</span>
|
<span v-if="project.date" class="text-sm text-gray-500 dark:text-gray-400 font-mono">{{ project.date }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white max-w-3xl tracking-tight">{{ project.title }}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<h1 class="text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white">{{ project.title }}</h1>
|
<!-- Content area -->
|
||||||
<p class="text-lg text-gray-500 dark:text-gray-400 leading-relaxed">{{ project.description }}</p>
|
<section class="py-12 md:py-16 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-10 lg:gap-16">
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="lg:col-span-2 space-y-14">
|
||||||
|
<!-- Description -->
|
||||||
|
<div>
|
||||||
|
<p class="text-lg sm:text-xl text-gray-600 dark:text-gray-300 leading-relaxed mb-8">{{ project.description }}</p>
|
||||||
|
|
||||||
<!-- CTA Buttons -->
|
<!-- CTA Buttons -->
|
||||||
<div class="flex flex-wrap gap-3 pt-2">
|
<div class="flex flex-wrap gap-3">
|
||||||
<UButton
|
<UButton
|
||||||
v-if="project.demoUrl"
|
v-if="project.demoUrl"
|
||||||
:to="project.demoUrl"
|
:to="project.demoUrl"
|
||||||
@@ -108,29 +120,23 @@ useSeoMeta({
|
|||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Content -->
|
|
||||||
<section class="py-16 px-4">
|
|
||||||
<div class="max-w-7xl mx-auto">
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-10 lg:gap-16">
|
|
||||||
<!-- Main Content -->
|
|
||||||
<div class="lg:col-span-2 space-y-16">
|
|
||||||
<!-- About -->
|
<!-- About -->
|
||||||
<div>
|
<div class="rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/60 dark:bg-gray-900/40 backdrop-blur-sm p-7 sm:p-8">
|
||||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.aboutProject') }}</h2>
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-5 flex items-center gap-3">
|
||||||
|
<div class="w-1 h-6 rounded-full bg-brand-500" />
|
||||||
|
{{ t('projects.projectDetail.aboutProject') }}
|
||||||
|
</h2>
|
||||||
<p class="text-gray-500 dark:text-gray-400 leading-relaxed text-lg">
|
<p class="text-gray-500 dark:text-gray-400 leading-relaxed text-lg">
|
||||||
{{ project.longDescription || project.description }}
|
{{ project.longDescription || project.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Features -->
|
<!-- Features -->
|
||||||
<div v-if="project.features" class="mt-8">
|
<div v-if="project.features" class="mt-8">
|
||||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4">{{ t('projects.projectDetail.keyFeatures') }}</h3>
|
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.keyFeatures') }}</h3>
|
||||||
<ul class="space-y-3">
|
<ul class="space-y-3">
|
||||||
<li v-for="feature in project.features" :key="feature" class="flex items-start gap-3">
|
<li v-for="feature in project.features" :key="feature" class="flex items-start gap-3 group">
|
||||||
<div class="w-6 h-6 rounded-full bg-brand-500/10 flex items-center justify-center shrink-0 mt-0.5">
|
<div class="w-6 h-6 rounded-lg bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center shrink-0 mt-0.5 group-hover:bg-brand-500/20 transition-colors">
|
||||||
<UIcon name="i-lucide-check" class="text-brand-500 w-3.5 h-3.5" />
|
<UIcon name="i-lucide-check" class="text-brand-500 w-3.5 h-3.5" />
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-600 dark:text-gray-300">{{ feature }}</span>
|
<span class="text-gray-600 dark:text-gray-300">{{ feature }}</span>
|
||||||
@@ -140,27 +146,33 @@ useSeoMeta({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Technologies -->
|
<!-- Technologies -->
|
||||||
<div v-if="project.technologies.length">
|
<div v-if="project.technologies.length" class="rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/60 dark:bg-gray-900/40 backdrop-blur-sm p-7 sm:p-8">
|
||||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.technologiesUsed') }}</h2>
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-5 flex items-center gap-3">
|
||||||
|
<div class="w-1 h-6 rounded-full bg-brand-500" />
|
||||||
|
{{ t('projects.projectDetail.technologiesUsed') }}
|
||||||
|
</h2>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<TechBadge v-for="tech in project.technologies" :key="tech" :tech="tech" />
|
<TechBadge v-for="tech in project.technologies" :key="tech" :tech="tech" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Gallery Thumbnails -->
|
<!-- Gallery Thumbnails -->
|
||||||
<div v-if="project.gallery?.length">
|
<div v-if="project.gallery?.length" class="rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/60 dark:bg-gray-900/40 backdrop-blur-sm p-7 sm:p-8">
|
||||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.gallery') }}</h2>
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-5 flex items-center gap-3">
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
<div class="w-1 h-6 rounded-full bg-brand-500" />
|
||||||
|
{{ t('projects.projectDetail.gallery') }}
|
||||||
|
</h2>
|
||||||
|
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||||
<button
|
<button
|
||||||
v-for="(image, index) in project.gallery"
|
v-for="(image, index) in project.gallery"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="relative rounded-xl overflow-hidden group cursor-pointer border border-gray-200 dark:border-gray-800"
|
class="relative rounded-xl overflow-hidden group cursor-pointer border border-gray-200/80 dark:border-gray-800/50 aspect-video"
|
||||||
@click="galleryRef?.openGallery(index)"
|
@click="galleryRef?.openGallery(index)"
|
||||||
>
|
>
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
:src="image"
|
:src="image"
|
||||||
:alt="`${project.title} - Image ${index + 1}`"
|
:alt="`${project.title} - Image ${index + 1}`"
|
||||||
class="w-full h-32 object-cover transition-transform duration-300 group-hover:scale-105"
|
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
format="webp"
|
format="webp"
|
||||||
/>
|
/>
|
||||||
@@ -173,17 +185,20 @@ useSeoMeta({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="space-y-6">
|
<aside class="sticky top-24 space-y-6">
|
||||||
<!-- Project Info Card -->
|
<!-- Project Info Card -->
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900 p-6 sticky top-24">
|
<div class="rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/60 dark:bg-gray-900/40 backdrop-blur-sm p-6">
|
||||||
<h3 class="font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.projectInfo') }}</h3>
|
<h3 class="font-bold text-gray-900 dark:text-white mb-5 flex items-center gap-2">
|
||||||
|
<UIcon name="i-lucide-info" class="text-brand-500 w-4 h-4" />
|
||||||
|
{{ t('projects.projectDetail.projectInfo') }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
<div class="space-y-4 text-sm">
|
<div class="space-y-4 text-sm">
|
||||||
<div v-if="project.date" class="flex justify-between items-center py-2 border-b border-gray-200 dark:border-gray-800">
|
<div v-if="project.date" class="flex justify-between items-center py-3 border-b border-gray-200/60 dark:border-gray-800/40">
|
||||||
<span class="text-gray-500 dark:text-gray-400">{{ t('projects.projectDetail.date') }}</span>
|
<span class="text-gray-500 dark:text-gray-400">{{ t('projects.projectDetail.date') }}</span>
|
||||||
<span class="font-semibold text-gray-900 dark:text-white">{{ project.date }}</span>
|
<span class="font-semibold text-gray-900 dark:text-white font-mono text-xs">{{ project.date }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="project.category" class="flex justify-between items-center py-2">
|
<div v-if="project.category" class="flex justify-between items-center py-3">
|
||||||
<span class="text-gray-500 dark:text-gray-400">{{ t('projects.projectDetail.category') }}</span>
|
<span class="text-gray-500 dark:text-gray-400">{{ t('projects.projectDetail.category') }}</span>
|
||||||
<UBadge variant="subtle" size="xs">{{ project.category }}</UBadge>
|
<UBadge variant="subtle" size="xs">{{ project.category }}</UBadge>
|
||||||
</div>
|
</div>
|
||||||
@@ -191,14 +206,17 @@ useSeoMeta({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Related Projects -->
|
<!-- Related Projects -->
|
||||||
<div v-if="relatedProjects.length > 0" class="rounded-2xl border border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900 p-6">
|
<div v-if="relatedProjects.length > 0" class="rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/60 dark:bg-gray-900/40 backdrop-blur-sm p-6">
|
||||||
<h3 class="font-bold text-gray-900 dark:text-white mb-5">{{ t('projects.projectDetail.relatedProjects') }}</h3>
|
<h3 class="font-bold text-gray-900 dark:text-white mb-5 flex items-center gap-2">
|
||||||
<div class="space-y-4">
|
<UIcon name="i-lucide-layers" class="text-brand-500 w-4 h-4" />
|
||||||
|
{{ t('projects.projectDetail.relatedProjects') }}
|
||||||
|
</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-for="related in relatedProjects"
|
v-for="related in relatedProjects"
|
||||||
:key="related.id"
|
:key="related.id"
|
||||||
:to="`/project/${related.id}`"
|
:to="`/project/${related.id}`"
|
||||||
class="flex gap-3 p-3 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors group"
|
class="flex gap-3 p-3 rounded-xl border border-transparent hover:border-brand-500/20 hover:bg-brand-500/5 transition-all duration-200 group"
|
||||||
>
|
>
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
v-if="related.image"
|
v-if="related.image"
|
||||||
@@ -211,7 +229,7 @@ useSeoMeta({
|
|||||||
/>
|
/>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<p class="font-semibold text-sm text-gray-900 dark:text-white truncate group-hover:text-brand-500 transition-colors">{{ related.title }}</p>
|
<p class="font-semibold text-sm text-gray-900 dark:text-white truncate group-hover:text-brand-500 transition-colors">{{ related.title }}</p>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 line-clamp-2 mt-0.5">{{ related.description }}</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 line-clamp-2 mt-1">{{ related.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+25
-22
@@ -53,37 +53,40 @@ function resetFilters() {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- Hero -->
|
<!-- Hero -->
|
||||||
<section class="pt-16 pb-12 px-4 bg-gray-50 dark:bg-gray-900/30">
|
<section class="relative pt-20 pb-16 px-4 sm:px-6 lg:px-8 overflow-hidden">
|
||||||
<div class="max-w-7xl mx-auto text-center">
|
<div class="absolute inset-0 bg-gray-50/80 dark:bg-gray-900/40" aria-hidden="true" />
|
||||||
<span class="text-sm font-semibold text-brand-500 dark:text-brand-400 uppercase tracking-wider">Portfolio</span>
|
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[400px] bg-brand-500/5 dark:bg-brand-500/8 rounded-full blur-3xl pointer-events-none" aria-hidden="true" />
|
||||||
<h1 class="text-4xl sm:text-5xl font-bold mt-2 mb-4 text-gray-900 dark:text-white">{{ t('projects.title') }}</h1>
|
|
||||||
<p class="text-lg text-gray-500 dark:text-gray-400 max-w-2xl mx-auto">{{ t('projects.subtitle') }}</p>
|
<div class="relative z-10 max-w-7xl mx-auto text-center">
|
||||||
|
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">// portfolio</span>
|
||||||
|
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold mt-3 mb-5 bg-gradient-to-r from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500 bg-clip-text text-transparent">{{ t('projects.title') }}</h1>
|
||||||
|
<p class="text-lg sm:text-xl text-gray-500 dark:text-gray-400 max-w-2xl mx-auto leading-relaxed">{{ t('projects.subtitle') }}</p>
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<div class="flex justify-center gap-10 mt-10">
|
<div class="flex justify-center gap-8 sm:gap-12 mt-12">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">{{ totalProjects }}</p>
|
<p class="text-3xl sm:text-4xl font-black bg-gradient-to-b from-brand-400 to-brand-600 bg-clip-text text-transparent">{{ totalProjects }}</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('nav.projects') }}</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-2 font-medium">{{ t('nav.projects') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-px bg-gray-200 dark:bg-gray-800" />
|
<div class="w-px bg-gradient-to-b from-transparent via-gray-300 dark:via-gray-700 to-transparent" />
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">{{ featuredCount }}</p>
|
<p class="text-3xl sm:text-4xl font-black bg-gradient-to-b from-brand-400 to-brand-600 bg-clip-text text-transparent">{{ featuredCount }}</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('home.featuredProjects.title') }}</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-2 font-medium">{{ t('home.featuredProjects.title') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-px bg-gray-200 dark:bg-gray-800" />
|
<div class="w-px bg-gradient-to-b from-transparent via-gray-300 dark:via-gray-700 to-transparent" />
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="text-3xl font-extrabold text-brand-500 dark:text-brand-400">{{ categories.length - 1 }}</p>
|
<p class="text-3xl sm:text-4xl font-black bg-gradient-to-b from-brand-400 to-brand-600 bg-clip-text text-transparent">{{ categories.length - 1 }}</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ t('projects.categories.all') }}</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-2 font-medium">{{ t('projects.categories.all') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Filters & Grid -->
|
<!-- Filters & Grid -->
|
||||||
<section class="py-12 px-4">
|
<section class="py-16 md:py-20 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="max-w-7xl mx-auto">
|
||||||
<!-- Filter bar -->
|
<!-- Filter bar -->
|
||||||
<div class="flex flex-col sm:flex-row gap-4 items-start sm:items-center mb-10 p-4 rounded-2xl bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-800">
|
<div class="flex flex-col sm:flex-row gap-4 items-start sm:items-center mb-12 p-4 sm:p-5 rounded-2xl border border-gray-200/80 dark:border-gray-800/50 bg-white/60 dark:bg-gray-900/40 backdrop-blur-sm">
|
||||||
<UInput
|
<UInput
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
icon="i-lucide-search"
|
icon="i-lucide-search"
|
||||||
@@ -108,18 +111,18 @@ function resetFilters() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Projects Grid -->
|
<!-- Projects Grid -->
|
||||||
<div v-if="filteredProjects.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
<div v-if="filteredProjects.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 lg:gap-6">
|
||||||
<ProjectCard v-for="project in filteredProjects" :key="project.id" :project="project" />
|
<ProjectCard v-for="project in filteredProjects" :key="project.id" :project="project" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Empty State -->
|
<!-- Empty State -->
|
||||||
<div v-else class="text-center py-24">
|
<div v-else class="text-center py-32">
|
||||||
<div class="w-16 h-16 mx-auto mb-6 rounded-2xl bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
|
<div class="w-16 h-16 mx-auto mb-6 rounded-2xl bg-gray-100 dark:bg-gray-800/60 border border-gray-200/80 dark:border-gray-700/30 flex items-center justify-center">
|
||||||
<UIcon name="i-lucide-search-x" class="text-2xl text-gray-400" />
|
<UIcon name="i-lucide-search-x" class="text-2xl text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ t('projects.noResults.title') }}</h3>
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">{{ t('projects.noResults.title') }}</h3>
|
||||||
<p class="text-gray-500 dark:text-gray-400 mb-8 max-w-md mx-auto">{{ t('projects.noResults.description') }}</p>
|
<p class="text-gray-500 dark:text-gray-400 mb-8 max-w-md mx-auto leading-relaxed">{{ t('projects.noResults.description') }}</p>
|
||||||
<UButton @click="resetFilters" variant="soft" size="md">
|
<UButton @click="resetFilters" variant="soft" size="md" icon="i-lucide-rotate-ccw">
|
||||||
{{ t('common.reset') }}
|
{{ t('common.reset') }}
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+26
-16
@@ -7,7 +7,7 @@
|
|||||||
"fiverr": "Fiverr"
|
"fiverr": "Fiverr"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"copyright": "\u00a9 2026 Killian Dalcin",
|
"copyright": "\u00a9 2026 Killian' DAL-CIN",
|
||||||
"navigation": "Quick Links",
|
"navigation": "Quick Links",
|
||||||
"services": "Services",
|
"services": "Services",
|
||||||
"legalNotices": "Legal Notices",
|
"legalNotices": "Legal Notices",
|
||||||
@@ -20,36 +20,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"a11y": {
|
"a11y": {
|
||||||
"logoLabel": "Killian Dalcin \u2014 Full Stack Developer \u2014 Back to homepage",
|
"logoLabel": "Killian' DAL-CIN \u2014 Full Stack Developer \u2014 Back to homepage",
|
||||||
"openMenu": "Open navigation menu",
|
"openMenu": "Open navigation menu",
|
||||||
"closeMenu": "Close navigation menu",
|
"closeMenu": "Close navigation menu",
|
||||||
"closeDrawer": "Close menu",
|
"closeDrawer": "Close menu",
|
||||||
"langToggle": "Change language \u2014 currently English",
|
"langToggle": "Change language \u2014 currently English",
|
||||||
"themeDark": "Switch to light mode",
|
"themeDark": "Switch to light mode",
|
||||||
"themeLight": "Switch to dark mode",
|
"themeLight": "Switch to dark mode",
|
||||||
"gitea": "Killian Dalcin on Gitea (opens in new tab)",
|
"gitea": "Killian' DAL-CIN on Gitea (opens in new tab)",
|
||||||
"linkedin": "Killian Dalcin on LinkedIn (opens in new tab)",
|
"linkedin": "Killian' DAL-CIN on LinkedIn (opens in new tab)",
|
||||||
"fiverr": "Killian Dalcin on Fiverr (opens in new tab)"
|
"fiverr": "Killian' DAL-CIN on Fiverr (opens in new tab)"
|
||||||
},
|
},
|
||||||
"seo": {
|
"seo": {
|
||||||
"home": {
|
"home": {
|
||||||
"title": "Killian Dalcin \u2014 Freelance Full Stack Developer",
|
"title": "Killian' DAL-CIN \u2014 Freelance Full Stack Developer",
|
||||||
"description": "Portfolio of Killian Dalcin, freelance full stack developer specializing in Vue.js, React and Node.js. High-performance web applications and custom solutions."
|
"description": "Portfolio of Killian' DAL-CIN, freelance full stack developer specializing in Vue.js, React and Node.js. High-performance web applications and custom solutions."
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"title": "Projects \u2014 Killian Dalcin",
|
"title": "Projects \u2014 Killian' DAL-CIN",
|
||||||
"description": "Discover my web development projects: Vue.js applications, Node.js APIs, Discord bots and enterprise solutions."
|
"description": "Discover my web development projects: Vue.js applications, Node.js APIs, Discord bots and enterprise solutions."
|
||||||
},
|
},
|
||||||
"about": {
|
"about": {
|
||||||
"title": "About \u2014 Killian Dalcin",
|
"title": "About \u2014 Killian' DAL-CIN",
|
||||||
"description": "Biography and skills of Killian Dalcin, freelance full stack developer based in France."
|
"description": "Biography and skills of Killian' DAL-CIN, freelance full stack developer based in France."
|
||||||
},
|
},
|
||||||
"contact": {
|
"contact": {
|
||||||
"title": "Contact \u2014 Killian Dalcin",
|
"title": "Contact \u2014 Killian' DAL-CIN",
|
||||||
"description": "Contact Killian Dalcin to discuss your web development project."
|
"description": "Contact Killian' DAL-CIN to discuss your web development project."
|
||||||
},
|
},
|
||||||
"fiverr": {
|
"fiverr": {
|
||||||
"title": "Fiverr Services \u2014 Killian Dalcin",
|
"title": "Fiverr Services \u2014 Killian' DAL-CIN",
|
||||||
"description": "Development services available on Fiverr: Discord bots, Minecraft plugins, web applications."
|
"description": "Development services available on Fiverr: Discord bots, Minecraft plugins, web applications."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"about": {
|
"about": {
|
||||||
"title": "About Killian - Full Stack Developer",
|
"title": "About Killian'- Full Stack Developer",
|
||||||
"subtitle": "Experienced web developer passionate about Vue.js, React, Node.js, and modern JavaScript technologies.",
|
"subtitle": "Experienced web developer passionate about Vue.js, React, Node.js, and modern JavaScript technologies.",
|
||||||
"intro": {
|
"intro": {
|
||||||
"title": "Professional Full Stack Developer",
|
"title": "Professional Full Stack Developer",
|
||||||
@@ -245,6 +245,10 @@
|
|||||||
"title": "They Transformed Their Business With My Services",
|
"title": "They Transformed Their Business With My Services",
|
||||||
"subtitle": "Join 500+ satisfied entrepreneurs. Average rating 5.0/5.0 across all my services."
|
"subtitle": "Join 500+ satisfied entrepreneurs. Average rating 5.0/5.0 across all my services."
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"title": "Fiverr FAQ",
|
||||||
|
"subtitle": "Everything you need to know before ordering my services on Fiverr."
|
||||||
|
},
|
||||||
"cta": {
|
"cta": {
|
||||||
"title": "Stop Searching, You Found THE Right Developer",
|
"title": "Stop Searching, You Found THE Right Developer",
|
||||||
"subtitle": "Every day without action = lost opportunities. Launch your project NOW.",
|
"subtitle": "Every day without action = lost opportunities. Launch your project NOW.",
|
||||||
@@ -263,7 +267,6 @@
|
|||||||
"findMeOn": "Connect on Social Media",
|
"findMeOn": "Connect on Social Media",
|
||||||
"methods": {
|
"methods": {
|
||||||
"email": "Email Address",
|
"email": "Email Address",
|
||||||
"phone": "Phone Number",
|
|
||||||
"location": "Location",
|
"location": "Location",
|
||||||
"responseTime": "Response within 24 hours",
|
"responseTime": "Response within 24 hours",
|
||||||
"availability": "Available for remote & freelance"
|
"availability": "Available for remote & freelance"
|
||||||
@@ -285,16 +288,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
|
"title": "Send me a message",
|
||||||
"name": "Your Name",
|
"name": "Your Name",
|
||||||
"email": "Email Address",
|
"email": "Email Address",
|
||||||
"subject": "Project Subject",
|
"subject": "Project Subject",
|
||||||
"message": "Project Details",
|
"message": "Project Details",
|
||||||
|
"submit": "Send Message",
|
||||||
"send": "Send Message",
|
"send": "Send Message",
|
||||||
"sending": "Sending...",
|
"sending": "Sending...",
|
||||||
"success": "Message sent successfully! I'll respond within 24 hours.",
|
"success": "Message sent successfully! I'll respond within 24 hours.",
|
||||||
"error": "Error sending message. Please try again or email directly.",
|
"error": "Error sending message. Please try again or email directly.",
|
||||||
"required": "This field is required",
|
"required": "This field is required",
|
||||||
"invalidEmail": "Please enter a valid email address"
|
"invalidEmail": "Please enter a valid email address",
|
||||||
|
"validation": {
|
||||||
|
"nameMin": "Name must be at least 2 characters",
|
||||||
|
"emailInvalid": "Please enter a valid email address",
|
||||||
|
"messageMin": "Message must be at least 10 characters"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Let's Build Something Great",
|
"title": "Let's Build Something Great",
|
||||||
|
|||||||
+26
-16
@@ -7,7 +7,7 @@
|
|||||||
"fiverr": "Fiverr"
|
"fiverr": "Fiverr"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"copyright": "\u00a9 2026 Killian Dalcin",
|
"copyright": "\u00a9 2026 Killian' DAL-CIN",
|
||||||
"navigation": "Liens Rapides",
|
"navigation": "Liens Rapides",
|
||||||
"services": "Services",
|
"services": "Services",
|
||||||
"legalNotices": "Mentions L\u00e9gales",
|
"legalNotices": "Mentions L\u00e9gales",
|
||||||
@@ -20,36 +20,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"a11y": {
|
"a11y": {
|
||||||
"logoLabel": "Killian Dalcin \u2014 Developpeur Full Stack \u2014 Retour a l'accueil",
|
"logoLabel": "Killian' DAL-CIN \u2014 Developpeur Full Stack \u2014 Retour a l'accueil",
|
||||||
"openMenu": "Ouvrir le menu de navigation",
|
"openMenu": "Ouvrir le menu de navigation",
|
||||||
"closeMenu": "Fermer le menu de navigation",
|
"closeMenu": "Fermer le menu de navigation",
|
||||||
"closeDrawer": "Fermer le menu",
|
"closeDrawer": "Fermer le menu",
|
||||||
"langToggle": "Changer la langue \u2014 actuellement Francais",
|
"langToggle": "Changer la langue \u2014 actuellement Francais",
|
||||||
"themeDark": "Activer le mode clair",
|
"themeDark": "Activer le mode clair",
|
||||||
"themeLight": "Activer le mode sombre",
|
"themeLight": "Activer le mode sombre",
|
||||||
"gitea": "Gitea de Killian Dalcin (nouvelle fenetre)",
|
"gitea": "Gitea de Killian' DAL-CIN (nouvelle fenetre)",
|
||||||
"linkedin": "LinkedIn de Killian Dalcin (nouvelle fenetre)",
|
"linkedin": "LinkedIn de Killian' DAL-CIN (nouvelle fenetre)",
|
||||||
"fiverr": "Fiverr de Killian Dalcin (nouvelle fenetre)"
|
"fiverr": "Fiverr de Killian' DAL-CIN (nouvelle fenetre)"
|
||||||
},
|
},
|
||||||
"seo": {
|
"seo": {
|
||||||
"home": {
|
"home": {
|
||||||
"title": "Killian Dalcin \u2014 Developpeur Full Stack Freelance",
|
"title": "Killian' DAL-CIN \u2014 Developpeur Full Stack Freelance",
|
||||||
"description": "Portfolio de Killian Dalcin, developpeur full stack freelance specialise en Vue.js, React et Node.js. Applications web performantes et solutions sur-mesure."
|
"description": "Portfolio de Killian' DAL-CIN, developpeur full stack freelance specialise en Vue.js, React et Node.js. Applications web performantes et solutions sur-mesure."
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"title": "Projets \u2014 Killian Dalcin",
|
"title": "Projets \u2014 Killian' DAL-CIN",
|
||||||
"description": "Decouvrez mes realisations en developpement web : applications Vue.js, API Node.js, bots Discord et solutions d'entreprise."
|
"description": "Decouvrez mes realisations en developpement web : applications Vue.js, API Node.js, bots Discord et solutions d'entreprise."
|
||||||
},
|
},
|
||||||
"about": {
|
"about": {
|
||||||
"title": "A propos \u2014 Killian Dalcin",
|
"title": "A propos \u2014 Killian' DAL-CIN",
|
||||||
"description": "Biographie et competences de Killian Dalcin, developpeur full stack freelance base en France."
|
"description": "Biographie et competences de Killian' DAL-CIN, developpeur full stack freelance base en France."
|
||||||
},
|
},
|
||||||
"contact": {
|
"contact": {
|
||||||
"title": "Contact \u2014 Killian Dalcin",
|
"title": "Contact \u2014 Killian' DAL-CIN",
|
||||||
"description": "Contactez Killian Dalcin pour discuter de votre projet de developpement web."
|
"description": "Contactez Killian' DAL-CIN pour discuter de votre projet de developpement web."
|
||||||
},
|
},
|
||||||
"fiverr": {
|
"fiverr": {
|
||||||
"title": "Services Fiverr \u2014 Killian Dalcin",
|
"title": "Services Fiverr \u2014 Killian' DAL-CIN",
|
||||||
"description": "Services de developpement disponibles sur Fiverr : bots Discord, plugins Minecraft, applications web."
|
"description": "Services de developpement disponibles sur Fiverr : bots Discord, plugins Minecraft, applications web."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"about": {
|
"about": {
|
||||||
"title": "\u00c0 propos de Killian - D\u00e9veloppeur Full Stack",
|
"title": "\u00c0 propos de Killian'- D\u00e9veloppeur Full Stack",
|
||||||
"subtitle": "D\u00e9veloppeur web exp\u00e9riment\u00e9 passionn\u00e9 par Vue.js, React, Node.js et les technologies JavaScript modernes.",
|
"subtitle": "D\u00e9veloppeur web exp\u00e9riment\u00e9 passionn\u00e9 par Vue.js, React, Node.js et les technologies JavaScript modernes.",
|
||||||
"intro": {
|
"intro": {
|
||||||
"title": "D\u00e9veloppeur Full Stack Professionnel",
|
"title": "D\u00e9veloppeur Full Stack Professionnel",
|
||||||
@@ -245,6 +245,10 @@
|
|||||||
"title": "Ils Ont Transform\u00e9 Leur Business Avec Mes Services",
|
"title": "Ils Ont Transform\u00e9 Leur Business Avec Mes Services",
|
||||||
"subtitle": "Rejoignez 500+ entrepreneurs satisfaits. Note moyenne 5.0/5.0 sur l'ensemble de mes services."
|
"subtitle": "Rejoignez 500+ entrepreneurs satisfaits. Note moyenne 5.0/5.0 sur l'ensemble de mes services."
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"title": "Questions Frequentes Fiverr",
|
||||||
|
"subtitle": "Tout ce que vous devez savoir avant de commander mes services sur Fiverr."
|
||||||
|
},
|
||||||
"cta": {
|
"cta": {
|
||||||
"title": "Arr\u00eatez de Chercher, Vous Avez Trouv\u00e9 LE Bon D\u00e9veloppeur",
|
"title": "Arr\u00eatez de Chercher, Vous Avez Trouv\u00e9 LE Bon D\u00e9veloppeur",
|
||||||
"subtitle": "Chaque jour sans agir = opportunit\u00e9s perdues. Lancez votre projet MAINTENANT.",
|
"subtitle": "Chaque jour sans agir = opportunit\u00e9s perdues. Lancez votre projet MAINTENANT.",
|
||||||
@@ -263,7 +267,6 @@
|
|||||||
"findMeOn": "Connectez-vous sur les R\u00e9seaux Sociaux",
|
"findMeOn": "Connectez-vous sur les R\u00e9seaux Sociaux",
|
||||||
"methods": {
|
"methods": {
|
||||||
"email": "Adresse Email",
|
"email": "Adresse Email",
|
||||||
"phone": "Num\u00e9ro de T\u00e9l\u00e9phone",
|
|
||||||
"location": "Localisation",
|
"location": "Localisation",
|
||||||
"responseTime": "R\u00e9ponse sous 24 heures",
|
"responseTime": "R\u00e9ponse sous 24 heures",
|
||||||
"availability": "Disponible pour remote & freelance"
|
"availability": "Disponible pour remote & freelance"
|
||||||
@@ -285,16 +288,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
|
"title": "Envoyez-moi un message",
|
||||||
"name": "Votre Nom",
|
"name": "Votre Nom",
|
||||||
"email": "Adresse Email",
|
"email": "Adresse Email",
|
||||||
"subject": "Sujet du Projet",
|
"subject": "Sujet du Projet",
|
||||||
"message": "D\u00e9tails du Projet",
|
"message": "D\u00e9tails du Projet",
|
||||||
|
"submit": "Envoyer le Message",
|
||||||
"send": "Envoyer le Message",
|
"send": "Envoyer le Message",
|
||||||
"sending": "Envoi en cours...",
|
"sending": "Envoi en cours...",
|
||||||
"success": "Message envoy\u00e9 avec succ\u00e8s ! Je r\u00e9pondrai dans les 24 heures.",
|
"success": "Message envoy\u00e9 avec succ\u00e8s ! Je r\u00e9pondrai dans les 24 heures.",
|
||||||
"error": "Erreur lors de l'envoi du message. Veuillez r\u00e9essayer ou envoyer un email directement.",
|
"error": "Erreur lors de l'envoi du message. Veuillez r\u00e9essayer ou envoyer un email directement.",
|
||||||
"required": "Ce champ est requis",
|
"required": "Ce champ est requis",
|
||||||
"invalidEmail": "Veuillez entrer une adresse email valide"
|
"invalidEmail": "Veuillez entrer une adresse email valide",
|
||||||
|
"validation": {
|
||||||
|
"nameMin": "Le nom doit contenir au moins 2 caracteres",
|
||||||
|
"emailInvalid": "Veuillez entrer une adresse email valide",
|
||||||
|
"messageMin": "Le message doit contenir au moins 10 caracteres"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Construisons Quelque Chose de Grand",
|
"title": "Construisons Quelque Chose de Grand",
|
||||||
|
|||||||
+4
-5
@@ -26,12 +26,11 @@ export default defineNuxtConfig({
|
|||||||
fallback: 'dark',
|
fallback: 'dark',
|
||||||
storage: 'cookie',
|
storage: 'cookie',
|
||||||
storageKey: 'nuxt-color-mode',
|
storageKey: 'nuxt-color-mode',
|
||||||
cookieName: 'nuxt-color-mode',
|
|
||||||
classSuffix: ''
|
classSuffix: ''
|
||||||
},
|
},
|
||||||
site: {
|
site: {
|
||||||
url: 'https://killiandalcin.fr',
|
url: 'https://killiandalcin.fr',
|
||||||
name: 'Killian Dalcin - Developpeur Full Stack'
|
name: "Killian' DAL-CIN - Developpeur Full Stack"
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
strategy: 'prefix_except_default',
|
strategy: 'prefix_except_default',
|
||||||
@@ -47,7 +46,7 @@ export default defineNuxtConfig({
|
|||||||
cookieKey: 'i18n_redirected',
|
cookieKey: 'i18n_redirected',
|
||||||
redirectOn: 'root',
|
redirectOn: 'root',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
smtpHost: '',
|
smtpHost: '',
|
||||||
smtpUser: '',
|
smtpUser: '',
|
||||||
@@ -58,9 +57,9 @@ export default defineNuxtConfig({
|
|||||||
id: '',
|
id: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
gtag: {
|
gtag: {
|
||||||
id: '',
|
id: '',
|
||||||
enabled: import.meta.env.NODE_ENV === 'production',
|
enabled: import.meta.env.NODE_ENV === 'production',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 228 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 262 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Killian - Full Stack Developer Portfolio",
|
"name": "Killian'- Full Stack Developer Portfolio",
|
||||||
"short_name": "Killian Portfolio",
|
"short_name": "Killian'Portfolio",
|
||||||
"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",
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ export interface FAQ {
|
|||||||
|
|
||||||
export interface ContactInfo {
|
export interface ContactInfo {
|
||||||
email: string
|
email: string
|
||||||
phone: string
|
|
||||||
location: string
|
location: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user