feat(hytale): implement Hytale plugin development page and related components
- Added a new `/hytale` page with sections for hero, services, and pricing. - Updated existing components to support Hytale-specific content and i18n. - Modified site configuration and state to reflect the new focus on Hytale plugin development. - Enhanced testimonials section to feature relevant client feedback. - Adjusted navigation to include a link to the new Hytale page.
This commit is contained in:
@@ -7,6 +7,7 @@ const mobileOpen = ref(false)
|
||||
|
||||
const navLinks = computed(() => [
|
||||
{ key: 'home', path: '/' },
|
||||
{ key: 'hytale', path: '/hytale' },
|
||||
{ key: 'projects', path: '/projects' },
|
||||
{ key: 'about', path: '/about' },
|
||||
{ key: 'contact', path: '/contact' },
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { siteConfig } from '~/data/site'
|
||||
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
const discordUrl = siteConfig.social.find(s => s.name === 'Discord')?.url ?? '#'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -27,16 +31,14 @@ const localePath = useLocalePath()
|
||||
<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>
|
||||
<span class="text-sm font-medium text-brand-700 dark:text-brand-400">Available for projects</span>
|
||||
<span class="text-sm font-medium text-brand-700 dark:text-brand-400">{{ t('home.badge.available') }}</span>
|
||||
</div>
|
||||
|
||||
<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]">
|
||||
<span class="text-gray-900 dark:text-white">{{ t('home.title').split(' ').slice(0, -2).join(' ') }}
|
||||
</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>
|
||||
t('home.title') }}</span>
|
||||
</h1>
|
||||
<p class="text-lg sm:text-xl text-gray-600 dark:text-gray-400 max-w-xl leading-relaxed">
|
||||
{{ t('home.subtitle') }}
|
||||
@@ -45,27 +47,23 @@ const localePath = useLocalePath()
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<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"
|
||||
<UButton
|
||||
:to="discordUrl"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
color="primary"
|
||||
icon="i-simple-icons-discord"
|
||||
size="lg"
|
||||
>
|
||||
{{ t('home.cta.viewProjects') }}
|
||||
<UIcon name="i-lucide-arrow-right" class="w-4 h-4" />
|
||||
</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') }}
|
||||
<UIcon name="i-lucide-external-link" class="w-4 h-4" />
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
{{ t('home.cta.discord') }}
|
||||
</UButton>
|
||||
<UButton
|
||||
: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"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
>
|
||||
{{ t('home.cta.contactMe') }}
|
||||
<UIcon name="i-lucide-message-circle" class="w-4 h-4" />
|
||||
</NuxtLink>
|
||||
{{ t('home.cta.contact') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -101,7 +99,7 @@ const localePath = useLocalePath()
|
||||
</div>
|
||||
<div class="pl-6">
|
||||
<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
|
||||
<span class="text-amber-600 dark:text-amber-400">'{{ t('home.terminal.role') }}'</span><span
|
||||
class="text-gray-500">,</span>
|
||||
</div>
|
||||
<div class="pl-6">
|
||||
@@ -144,12 +142,12 @@ const localePath = useLocalePath()
|
||||
<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="text-gray-700 dark:text-gray-300">50+ projects</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">{{ t('home.stats.projects') }}</span>
|
||||
</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">
|
||||
<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">{{ t('home.stats.rating') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { testimonials, testimonialsStats } from '~/data/testimonials'
|
||||
|
||||
const props = defineProps<{
|
||||
featured?: boolean
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const displayed = computed(() => props.featured ? testimonials.filter(t => t.featured) : testimonials)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -34,7 +40,7 @@ const { t } = useI18n()
|
||||
<!-- Horizontal scrolling testimonials -->
|
||||
<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
|
||||
v-for="(testimonial, index) in testimonials"
|
||||
v-for="(testimonial, index) in displayed"
|
||||
:key="index"
|
||||
class="flex-none w-[340px] sm:w-[400px] snap-start"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-16 md:py-24 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<span class="font-mono text-sm text-brand-500 dark:text-brand-400 tracking-wider">{{ t('hytale.hero.label') }}</span>
|
||||
<h1 class="text-4xl sm:text-5xl md:text-6xl font-extrabold tracking-tight leading-[1.1] mt-4">
|
||||
<span class="bg-gradient-to-r from-brand-500 via-brand-400 to-emerald-400 bg-clip-text text-transparent">{{ t('hytale.hero.title') }}</span>
|
||||
</h1>
|
||||
<p class="text-lg sm:text-xl text-gray-500 dark:text-gray-400 mt-6 max-w-2xl mx-auto leading-relaxed">
|
||||
{{ t('hytale.hero.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { hytalePricing } from '~/data/pricing'
|
||||
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-16 md:py-24 px-4 sm:px-6 lg:px-8">
|
||||
<div class="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">{{ t('hytale.pricing.label') }}</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('hytale.pricing.title') }}</h2>
|
||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-4 max-w-2xl mx-auto leading-relaxed">{{ t('hytale.pricing.subtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<UCard
|
||||
v-for="tier in hytalePricing"
|
||||
:key="tier.id"
|
||||
class="hover:border-brand-500/40 hover:shadow-xl hover:shadow-brand-500/10 hover:-translate-y-1 transition-all duration-200"
|
||||
:class="{ 'ring-2 ring-brand-500': tier.featured }"
|
||||
>
|
||||
<div class="flex flex-col gap-4 p-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">{{ t(`hytale.pricing.${tier.id}.name`) }}</h3>
|
||||
<UBadge v-if="tier.featured" color="primary" variant="subtle">{{ t('hytale.pricing.popular') }}</UBadge>
|
||||
</div>
|
||||
|
||||
<div class="text-3xl font-extrabold text-gray-900 dark:text-white">
|
||||
<template v-if="tier.priceFixed">
|
||||
<span class="text-sm font-normal text-gray-500 dark:text-gray-400">{{ t('hytale.pricing.from') }} </span>
|
||||
{{ tier.priceFixed }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-lg">{{ t('hytale.pricing.onQuote') }}</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ t(`hytale.pricing.${tier.id}.description`) }}</p>
|
||||
|
||||
<ul class="space-y-2 flex-1">
|
||||
<li v-for="i in 4" :key="i" class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
|
||||
<UIcon name="i-lucide-check" class="w-4 h-4 text-brand-500 shrink-0" />
|
||||
{{ t(`hytale.pricing.${tier.id}.features.${i - 1}`) }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<UButton
|
||||
:to="localePath('/contact')"
|
||||
:color="tier.featured ? 'primary' : 'neutral'"
|
||||
:variant="tier.featured ? 'solid' : 'outline'"
|
||||
block
|
||||
class="mt-4"
|
||||
>
|
||||
{{ t('hytale.pricing.cta') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
|
||||
const services = [
|
||||
{ id: 'plugin', icon: 'i-lucide-puzzle' },
|
||||
{ id: 'config', icon: 'i-lucide-settings' },
|
||||
{ id: 'support', icon: 'i-lucide-shield-check' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-16 md:py-24 px-4 sm:px-6 lg:px-8 bg-gray-50/50 dark:bg-gray-900/20">
|
||||
<div class="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">{{ t('hytale.services.label') }}</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('hytale.services.title') }}</h2>
|
||||
<p class="text-lg text-gray-500 dark:text-gray-400 mt-4 max-w-2xl mx-auto leading-relaxed">{{ t('hytale.services.subtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<UCard v-for="service in services" :key="service.id" class="hover:border-brand-500/40 hover:shadow-xl hover:shadow-brand-500/10 hover:-translate-y-1 transition-all duration-200">
|
||||
<div class="flex flex-col items-center text-center gap-4 p-2">
|
||||
<div class="w-12 h-12 rounded-xl bg-brand-500/10 dark:bg-brand-500/15 flex items-center justify-center">
|
||||
<UIcon :name="service.icon" class="w-6 h-6 text-brand-500" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ t(`hytale.services.items.${service.id}.title`) }}</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 leading-relaxed">{{ t(`hytale.services.items.${service.id}.description`) }}</p>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { PricingTier } from '~~/shared/types'
|
||||
|
||||
export const hytalePricing: PricingTier[] = [
|
||||
{ id: 'simple', priceFixed: '50€', featured: false },
|
||||
{ id: 'complex', priceFixed: null, priceLabel: 'Sur devis', featured: true },
|
||||
{ id: 'custom', priceFixed: null, priceLabel: 'Sur devis', featured: false },
|
||||
{ id: 'maintenance', priceFixed: '30€/mois', featured: false },
|
||||
{ id: 'web', priceFixed: null, priceLabel: 'Sur devis', featured: false },
|
||||
]
|
||||
+4
-3
@@ -4,9 +4,10 @@ export type { SiteConfig, ContactInfo, SocialLink, FiverrService, FiverrConfig }
|
||||
|
||||
export const siteConfig: SiteConfig = {
|
||||
name: 'Killian',
|
||||
title: "Killian' DAL-CIN - Full Stack Developer | Vue.js, React, Node.js Expert",
|
||||
title: "Killian' DAL-CIN - Hytale Plugin Developer | Freelance",
|
||||
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.',
|
||||
jobTitle: 'Hytale Plugin Developer',
|
||||
author: 'Killian',
|
||||
url: 'https://killiandalcin.fr',
|
||||
|
||||
@@ -91,12 +92,12 @@ export const siteConfig: SiteConfig = {
|
||||
},
|
||||
organization: {
|
||||
'@type': 'ProfessionalService',
|
||||
name: "Killian' DAL-CIN - Developpeur Full Stack",
|
||||
name: "Killian' DAL-CIN - Hytale Plugin Developer",
|
||||
logo: 'https://killiandalcin.fr/logo.webp',
|
||||
priceRange: '$$$',
|
||||
aggregateRating: {
|
||||
ratingValue: '5',
|
||||
reviewCount: '10',
|
||||
reviewCount: '5',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,6 +25,7 @@ export const testimonials: Testimonial[] = [
|
||||
"Travail excellent, Communication au top, Disponible en tout temps, réactif et à l'écoute je le recommande vivement et reviendrai vers lui si je dois refaire un projet similaire !",
|
||||
date: '22/04/2023',
|
||||
platform: 'Fiverr',
|
||||
featured: true,
|
||||
project_type: 'Bot Discord',
|
||||
results: ["Prix: Jusqu'à 50€", 'Durée: 4 jours', 'Communication parfaite'],
|
||||
},
|
||||
@@ -51,6 +52,7 @@ export const testimonials: Testimonial[] = [
|
||||
'Excellent développeur, la commande fut plus rapide que prévu la communication est instantané et le résultat est parfait. Je recommande fortement et reviendrai sûrement pour des mise à jour !',
|
||||
date: '12/11/2022',
|
||||
platform: 'Fiverr',
|
||||
featured: true,
|
||||
project_type: 'Bot Discord',
|
||||
results: [
|
||||
'Livraison plus rapide que prévu',
|
||||
@@ -73,7 +75,7 @@ export const testimonials: Testimonial[] = [
|
||||
]
|
||||
|
||||
export const testimonialsStats: TestimonialsStats = {
|
||||
totalReviews: 10,
|
||||
totalReviews: 5,
|
||||
averageRating: 5.0,
|
||||
projectsCompleted: 25,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
|
||||
useSeoMeta({
|
||||
title: () => t('seo.hytale.title'),
|
||||
description: () => t('seo.hytale.description'),
|
||||
ogTitle: () => t('seo.hytale.title'),
|
||||
ogDescription: () => t('seo.hytale.description'),
|
||||
ogImage: 'https://killiandalcin.fr/og-image.png',
|
||||
ogType: 'website',
|
||||
})
|
||||
|
||||
useHead({
|
||||
script: [{
|
||||
type: 'application/ld+json',
|
||||
innerHTML: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Service',
|
||||
name: 'Hytale Plugin Development',
|
||||
provider: {
|
||||
'@type': 'Person',
|
||||
name: "Killian' DAL-CIN",
|
||||
jobTitle: 'Hytale Plugin Developer',
|
||||
},
|
||||
}),
|
||||
}],
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<HytaleHeroSection />
|
||||
<HytaleServicesSection />
|
||||
<HytalePricingSection />
|
||||
<div class="relative bg-gray-50/50 dark:bg-gray-900/20">
|
||||
<TestimonialsSection />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
+1
-1
@@ -63,7 +63,7 @@ useHead({
|
||||
|
||||
<!-- Testimonials Section -->
|
||||
<div class="relative bg-gray-50/50 dark:bg-gray-900/20">
|
||||
<TestimonialsSection />
|
||||
<TestimonialsSection featured />
|
||||
</div>
|
||||
|
||||
<!-- FAQ Section -->
|
||||
|
||||
Reference in New Issue
Block a user