
- Mise à jour des métadonnées SEO dans index.html pour un meilleur référencement - Ajout de la prise en charge des balises Open Graph et Twitter pour les partages sur les réseaux sociaux - Intégration de données structurées pour les pages About, Fiverr et Home - Ajout d'un fichier robots.txt pour contrôler l'accès des robots d'indexation - Création d'un fichier sitemap.xml pour améliorer la découverte des pages par les moteurs de recherche - Ajout d'un fichier site.webmanifest pour la prise en charge des applications web progressives - Mise à jour des traductions pour refléter les changements dans le contenu et les services - Amélioration de l'accessibilité avec des attributs ARIA dans les composants de l'interface utilisateur
106 lines
2.8 KiB
Vue
106 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import { type Technology } from '@/types'
|
|
import { useAssets } from '@/composables/useAssets'
|
|
import { techStack } from '@/data/techstack'
|
|
|
|
interface Props {
|
|
tech: Technology | string
|
|
showLevel?: boolean
|
|
showImage?: boolean
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
showLevel: true,
|
|
showImage: true
|
|
})
|
|
|
|
const { getImageUrl } = useAssets()
|
|
|
|
// Get the technology data (handle both string and object)
|
|
const techData = computed((): Technology => {
|
|
if (typeof props.tech === 'string') {
|
|
const techName = props.tech as string
|
|
|
|
// Create a mapping for technologies that don't match exactly
|
|
const techMapping: Record<string, string> = {
|
|
'Three.js': 'JavaScript',
|
|
'WebGL': 'JavaScript',
|
|
'Discord.js': 'JavaScript',
|
|
'Express': 'Node.js',
|
|
'Canvas': 'JavaScript',
|
|
'Insta.js': 'JavaScript',
|
|
'Instagram API': 'JavaScript',
|
|
'Crowdin API': 'JavaScript',
|
|
'Cron': 'Node.js'
|
|
}
|
|
|
|
// Try to find the exact match first
|
|
let foundTech = Object.values(techStack)
|
|
.flat()
|
|
.find(t => t.name.toLowerCase() === techName.toLowerCase())
|
|
|
|
// If not found, try the mapping
|
|
if (!foundTech && techMapping[techName]) {
|
|
const mappedName = techMapping[techName]
|
|
foundTech = Object.values(techStack)
|
|
.flat()
|
|
.find(t => t.name.toLowerCase() === mappedName.toLowerCase())
|
|
}
|
|
|
|
if (foundTech) {
|
|
return foundTech
|
|
}
|
|
|
|
// Fallback: create a basic tech object from string
|
|
return {
|
|
name: techName,
|
|
image: '', // No image for unknown techs
|
|
level: 'Intermediate' as const
|
|
}
|
|
}
|
|
|
|
return props.tech as Technology
|
|
})
|
|
|
|
// Get the actual image URL
|
|
const imageUrl = computed(() => {
|
|
if (!techData.value.image) return ''
|
|
return getImageUrl(techData.value.image)
|
|
})
|
|
|
|
const getLevelColor = (level: Technology['level']) => {
|
|
switch (level) {
|
|
case 'Advanced':
|
|
return 'badge-success'
|
|
case 'Intermediate':
|
|
return 'badge-primary'
|
|
case 'Beginner':
|
|
return 'badge-secondary'
|
|
default:
|
|
return 'badge-secondary'
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="tech-badge" itemscope itemtype="https://schema.org/ComputerLanguage">
|
|
<!-- Tech image -->
|
|
<img v-if="showImage && imageUrl" :src="imageUrl" :alt="`${techData.name} programming language logo`"
|
|
class="tech-image" loading="lazy" width="24" height="24" itemprop="image">
|
|
|
|
<!-- Tech name -->
|
|
<span class="tech-name" itemprop="name">{{ techData.name }}</span>
|
|
|
|
<!-- Level indicator -->
|
|
<span v-if="showLevel" :class="['badge', getLevelColor(techData.level)]" class="tech-level"
|
|
:aria-label="`Skill level: ${techData.level}`">
|
|
{{ techData.level }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
@import './styles/TechBadge.css';
|
|
</style>
|