refactor(config): update nuxt.config.ts to enhance module configuration, remove deprecated files, and improve contact form validation with zod schema
This commit is contained in:
@@ -46,8 +46,8 @@ defineExpose({ openGallery })
|
||||
icon="i-lucide-x"
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
@click="isOpen = false"
|
||||
:aria-label="'Close gallery'"
|
||||
@click="isOpen = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -35,8 +35,9 @@ const techData = computed((): Technology => {
|
||||
|
||||
let found = allTechs.find((t) => t.name.toLowerCase() === techName.toLowerCase())
|
||||
|
||||
if (!found && techMapping[techName]) {
|
||||
found = allTechs.find((t) => t.name.toLowerCase() === techMapping[techName].toLowerCase())
|
||||
const mapped = techMapping[techName]
|
||||
if (!found && mapped) {
|
||||
found = allTechs.find((t) => t.name.toLowerCase() === mapped.toLowerCase())
|
||||
}
|
||||
|
||||
return found ?? { name: techName, image: '', level: 'Intermediate' as const }
|
||||
|
||||
@@ -33,7 +33,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
/>
|
||||
</svg>
|
||||
</summary>
|
||||
<div class="px-4 py-3 prose prose-neutral dark:prose-invert max-w-none
|
||||
<div
|
||||
class="px-4 py-3 prose prose-neutral dark:prose-invert max-w-none
|
||||
prose-code:before:content-none prose-code:after:content-none
|
||||
prose-pre:p-0 prose-pre:bg-transparent text-sm">
|
||||
<slot />
|
||||
|
||||
@@ -50,7 +50,7 @@ const wrapperStyle = computed(() => {
|
||||
:title="props.title || props.caption"
|
||||
loading="lazy"
|
||||
:class="props.align === 'full' && !attrs.class ? 'w-full rounded-lg' : 'rounded-lg'"
|
||||
/>
|
||||
>
|
||||
<span
|
||||
v-if="props.caption"
|
||||
class="mt-2 block text-center text-xs text-neutral-500 dark:text-neutral-400 italic"
|
||||
|
||||
@@ -24,7 +24,8 @@ const quickLinks = computed(() => [
|
||||
<!-- Brand column -->
|
||||
<div class="sm:col-span-2 lg:col-span-1 space-y-5">
|
||||
<NuxtLink :to="localePath('/')" class="flex items-center gap-2.5 group">
|
||||
<NuxtImg src="/images/logo.webp" alt="Killian' DAL-CIN" width="36" height="36" loading="lazy"
|
||||
<NuxtImg
|
||||
src="/images/logo.webp" alt="Killian' DAL-CIN" width="36" height="36" loading="lazy"
|
||||
class="rounded-lg transition-transform duration-300 group-hover:scale-110" />
|
||||
<span class="text-lg font-bold text-gray-900 dark:text-white">Killian' DAL-CIN</span>
|
||||
</NuxtLink>
|
||||
@@ -39,7 +40,8 @@ const quickLinks = computed(() => [
|
||||
Navigation
|
||||
</h3>
|
||||
<nav class="flex flex-col gap-3">
|
||||
<NuxtLink v-for="link in quickLinks" :key="link.key" :to="localePath(link.path)"
|
||||
<NuxtLink
|
||||
v-for="link in quickLinks" :key="link.key" :to="localePath(link.path)"
|
||||
class="text-sm text-gray-600 dark:text-gray-400 hover:text-brand-500 dark:hover:text-brand-400 transition-colors duration-200">
|
||||
{{ t(`nav.${link.key}`) }}
|
||||
</NuxtLink>
|
||||
@@ -65,10 +67,12 @@ const quickLinks = computed(() => [
|
||||
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"
|
||||
<a
|
||||
v-for="link in socialLinks" :key="link.name" :href="link.url" target="_blank" rel="noopener noreferrer"
|
||||
:aria-label="t(link.ariaKey)"
|
||||
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-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>
|
||||
</div>
|
||||
|
||||
@@ -34,14 +34,16 @@ function isActive(path: string): boolean {
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<!-- Logo -->
|
||||
<NuxtLink :to="localePath('/')" :aria-label="t('a11y.logoLabel')" class="flex items-center gap-2.5 shrink-0">
|
||||
<NuxtImg src="/images/logo.webp" alt="Killian' DAL-CIN" width="36" height="36" loading="eager"
|
||||
<NuxtImg
|
||||
src="/images/logo.webp" alt="Killian' DAL-CIN" 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>
|
||||
|
||||
<!-- Desktop nav -->
|
||||
<nav class="hidden md:flex items-center gap-1" aria-label="Main navigation">
|
||||
<NuxtLink v-for="link in navLinks" :key="link.key" :to="localePath(link.path)"
|
||||
<NuxtLink
|
||||
v-for="link in navLinks" :key="link.key" :to="localePath(link.path)"
|
||||
:aria-current="isActive(link.path) ? 'page' : undefined"
|
||||
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors" :class="[
|
||||
isActive(link.path)
|
||||
@@ -60,13 +62,15 @@ function isActive(path: string): boolean {
|
||||
</UButton>
|
||||
|
||||
<!-- Theme toggle -->
|
||||
<UButton variant="ghost" color="neutral" size="sm"
|
||||
<UButton
|
||||
variant="ghost" color="neutral" size="sm"
|
||||
:icon="colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon'"
|
||||
:aria-label="colorMode.value === 'dark' ? t('a11y.themeDark') : t('a11y.themeLight')"
|
||||
@click="toggleTheme" />
|
||||
|
||||
<!-- Mobile hamburger -->
|
||||
<UButton variant="ghost" color="neutral" size="sm" icon="i-lucide-menu" class="md:hidden"
|
||||
<UButton
|
||||
variant="ghost" color="neutral" size="sm" icon="i-lucide-menu" class="md:hidden"
|
||||
:aria-label="t('a11y.openMenu')" @click="mobileOpen = true" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,7 +87,8 @@ function isActive(path: string): boolean {
|
||||
|
||||
<template #body>
|
||||
<nav class="flex flex-col gap-1" aria-label="Mobile navigation">
|
||||
<NuxtLink v-for="link in navLinks" :key="link.key" :to="localePath(link.path)"
|
||||
<NuxtLink
|
||||
v-for="link in navLinks" :key="link.key" :to="localePath(link.path)"
|
||||
:aria-current="isActive(link.path) ? 'page' : undefined"
|
||||
class="px-4 py-3 text-base font-medium rounded-lg transition-colors" :class="[
|
||||
isActive(link.path)
|
||||
@@ -100,7 +105,8 @@ function isActive(path: string): boolean {
|
||||
<UButton variant="ghost" color="neutral" :aria-label="t('a11y.langToggle')" @click="toggleLocale">
|
||||
{{ locale === 'fr' ? 'EN' : 'FR' }}
|
||||
</UButton>
|
||||
<UButton variant="ghost" color="neutral" :icon="colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon'"
|
||||
<UButton
|
||||
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')"
|
||||
@click="toggleTheme" />
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,8 @@ const resolvedSecondaryText = computed(() => props.secondaryText || t('home.cta2
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<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">
|
||||
<!-- Subtle dot 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"
|
||||
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" />
|
||||
|
||||
@@ -11,7 +11,8 @@ const discordUrl = siteConfig.social.find(s => s.name === 'Discord')?.url ?? '#'
|
||||
<section class="relative min-h-[80vh] flex items-center overflow-hidden bg-white dark:bg-gray-950">
|
||||
<!-- Dot grid background pattern -->
|
||||
<div class="absolute inset-0 opacity-[0.03] dark:opacity-[0.06]" aria-hidden="true">
|
||||
<div class="absolute inset-0"
|
||||
<div
|
||||
class="absolute inset-0"
|
||||
style="background-image: radial-gradient(circle, currentColor 1px, transparent 1px); background-size: 32px 32px;" />
|
||||
</div>
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { NuxtError } from '#app'
|
||||
|
||||
const props = defineProps<{ error: NuxtError }>()
|
||||
defineProps<{ error: NuxtError }>()
|
||||
const { t } = useI18n()
|
||||
|
||||
function handleError() {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { techStack } from '~/data/techstack'
|
||||
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
useSeoMeta({
|
||||
title: () => t('seo.about.title'),
|
||||
|
||||
@@ -122,7 +122,7 @@ function resetFilters() {
|
||||
</div>
|
||||
<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 leading-relaxed">{{ t('projects.noResults.description') }}</p>
|
||||
<UButton @click="resetFilters" variant="soft" size="md" icon="i-lucide-rotate-ccw">
|
||||
<UButton variant="soft" size="md" icon="i-lucide-rotate-ccw" @click="resetFilters">
|
||||
{{ t('common.reset') }}
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
+2
-1
@@ -19,7 +19,8 @@ const { data: page } = await useAsyncData('test', () =>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<article class="prose prose-neutral dark:prose-invert max-w-none
|
||||
<article
|
||||
class="prose prose-neutral dark:prose-invert max-w-none
|
||||
prose-headings:font-semibold
|
||||
prose-code:before:content-none prose-code:after:content-none
|
||||
prose-pre:p-0 prose-pre:bg-transparent">
|
||||
|
||||
Reference in New Issue
Block a user