✨ feat(frontend): composant NarratorBubble avec 5 stages du Bug
Story 3.2 : Implémentation du narrateur-guide "Le Bug" - Composant NarratorBubble.vue avec effet typewriter - 5 SVG représentant l'évolution de la mascotte (silhouette à révélation) - Animation slide-up/fade-out avec prefers-reduced-motion - Support clavier (Espace/Entrée pour skip, Échap pour fermer) - Accessibilité (aria-live, role="status", sr-only) - Responsive (position adaptée mobile avec bottom-bar) - Traductions narrator.clickToSkip et narrator.bugAlt Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Story 3.2: Composant NarratorBubble (Le Bug)
|
# Story 3.2: Composant NarratorBubble (Le Bug)
|
||||||
|
|
||||||
Status: ready-for-dev
|
Status: review
|
||||||
|
|
||||||
## Story
|
## Story
|
||||||
|
|
||||||
@@ -22,55 +22,55 @@ so that je me sens guidé et l'expérience est immersive.
|
|||||||
|
|
||||||
## Tasks / Subtasks
|
## Tasks / Subtasks
|
||||||
|
|
||||||
- [ ] **Task 1: Créer le composable useTypewriter** (AC: #3, #4, #7)
|
- [x] **Task 1: Créer le composable useTypewriter** (AC: #3, #4, #7)
|
||||||
- [ ] Créer `frontend/app/composables/useTypewriter.ts`
|
- [x] Créer `frontend/app/composables/useTypewriter.ts` (existait déjà de Story 2.7)
|
||||||
- [ ] Accepter le texte en paramètre
|
- [x] Accepter le texte en paramètre
|
||||||
- [ ] Afficher lettre par lettre (30-50ms par lettre)
|
- [x] Afficher lettre par lettre (30-50ms par lettre)
|
||||||
- [ ] Exposer une méthode `skip()` pour afficher tout le texte instantanément
|
- [x] Exposer une méthode `skip()` pour afficher tout le texte instantanément
|
||||||
- [ ] Respecter `prefers-reduced-motion`
|
- [x] Respecter `prefers-reduced-motion`
|
||||||
|
|
||||||
- [ ] **Task 2: Créer les assets du Bug par stage** (AC: #2)
|
- [x] **Task 2: Créer les assets du Bug par stage** (AC: #2)
|
||||||
- [ ] Préparer 5 images SVG ou PNG pour les 5 stades du Bug
|
- [x] Préparer 5 images SVG ou PNG pour les 5 stades du Bug
|
||||||
- [ ] Stage 1 : silhouette sombre floue
|
- [x] Stage 1 : silhouette sombre floue
|
||||||
- [ ] Stage 2 : forme vague avec yeux
|
- [x] Stage 2 : forme vague avec yeux
|
||||||
- [ ] Stage 3 : pattes visibles
|
- [x] Stage 3 : pattes visibles
|
||||||
- [ ] Stage 4 : araignée reconnaissable
|
- [x] Stage 4 : araignée reconnaissable
|
||||||
- [ ] Stage 5 : mascotte complète révélée
|
- [x] Stage 5 : mascotte complète révélée
|
||||||
- [ ] Placer dans `frontend/public/images/bug/`
|
- [x] Placer dans `frontend/public/images/bug/`
|
||||||
|
|
||||||
- [ ] **Task 3: Créer le composant NarratorBubble** (AC: #1, #2, #3, #4, #5, #8, #9)
|
- [x] **Task 3: Créer le composant NarratorBubble** (AC: #1, #2, #3, #4, #5, #8, #9)
|
||||||
- [ ] Créer `frontend/app/components/feature/NarratorBubble.vue`
|
- [x] Créer `frontend/app/components/feature/NarratorBubble.vue`
|
||||||
- [ ] Props : message (string), visible (boolean)
|
- [x] Props : message (string), visible (boolean)
|
||||||
- [ ] Emit : close, skip
|
- [x] Emit : close, skip
|
||||||
- [ ] Afficher l'avatar du Bug selon `narratorStage` du store
|
- [x] Afficher l'avatar du Bug selon `narratorStage` du store
|
||||||
- [ ] Intégrer le composable useTypewriter
|
- [x] Intégrer le composable useTypewriter
|
||||||
- [ ] Bouton de fermeture/minimisation
|
- [x] Bouton de fermeture/minimisation
|
||||||
- [ ] Utiliser font-narrative pour le texte
|
- [x] Utiliser font-narrative pour le texte
|
||||||
|
|
||||||
- [ ] **Task 4: Implémenter l'accessibilité** (AC: #6, #7)
|
- [x] **Task 4: Implémenter l'accessibilité** (AC: #6, #7)
|
||||||
- [ ] Ajouter `aria-live="polite"` sur le conteneur
|
- [x] Ajouter `aria-live="polite"` sur le conteneur
|
||||||
- [ ] Ajouter `role="status"` pour signaler les mises à jour
|
- [x] Ajouter `role="status"` pour signaler les mises à jour
|
||||||
- [ ] S'assurer que le texte complet est accessible même pendant l'animation
|
- [x] S'assurer que le texte complet est accessible même pendant l'animation
|
||||||
- [ ] Tester avec prefers-reduced-motion
|
- [x] Tester avec prefers-reduced-motion
|
||||||
|
|
||||||
- [ ] **Task 5: Animation d'apparition/disparition** (AC: #9)
|
- [x] **Task 5: Animation d'apparition/disparition** (AC: #9)
|
||||||
- [ ] Slide-up pour l'apparition
|
- [x] Slide-up pour l'apparition
|
||||||
- [ ] Fade-out pour la disparition
|
- [x] Fade-out pour la disparition
|
||||||
- [ ] Utiliser CSS transitions pour fluidité
|
- [x] Utiliser CSS transitions pour fluidité
|
||||||
- [ ] Non-bloquante : ne pas empêcher les interactions avec le reste de la page
|
- [x] Non-bloquante : ne pas empêcher les interactions avec le reste de la page
|
||||||
|
|
||||||
- [ ] **Task 6: Responsive design** (AC: #1)
|
- [x] **Task 6: Responsive design** (AC: #1)
|
||||||
- [ ] Desktop : bulle en bas de l'écran (position fixed)
|
- [x] Desktop : bulle en bas de l'écran (position fixed)
|
||||||
- [ ] Mobile : au-dessus de la bottom bar (variable CSS pour le spacing)
|
- [x] Mobile : au-dessus de la bottom bar (variable CSS pour le spacing)
|
||||||
- [ ] Taille adaptée à l'écran
|
- [x] Taille adaptée à l'écran
|
||||||
|
|
||||||
- [ ] **Task 7: Tests et validation**
|
- [x] **Task 7: Tests et validation**
|
||||||
- [ ] Tester l'effet typewriter
|
- [x] Tester l'effet typewriter
|
||||||
- [ ] Tester le skip au clic/Espace
|
- [x] Tester le skip au clic/Espace
|
||||||
- [ ] Vérifier les 5 stades du Bug
|
- [x] Vérifier les 5 stades du Bug
|
||||||
- [ ] Valider l'accessibilité (screen reader)
|
- [x] Valider l'accessibilité (screen reader)
|
||||||
- [ ] Tester prefers-reduced-motion
|
- [x] Tester prefers-reduced-motion
|
||||||
- [ ] Valider responsive (desktop/mobile)
|
- [x] Valider responsive (desktop/mobile)
|
||||||
|
|
||||||
## Dev Notes
|
## Dev Notes
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ development_status:
|
|||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
epic-3: in-progress
|
epic-3: in-progress
|
||||||
3-1-table-narrator-texts-api-narrateur: review
|
3-1-table-narrator-texts-api-narrateur: review
|
||||||
3-2-composant-narratorbubble-le-bug: ready-for-dev
|
3-2-composant-narratorbubble-le-bug: review
|
||||||
3-3-textes-narrateur-contextuels-arc-revelation: ready-for-dev
|
3-3-textes-narrateur-contextuels-arc-revelation: ready-for-dev
|
||||||
3-4-barre-progression-globale-xp-bar: ready-for-dev
|
3-4-barre-progression-globale-xp-bar: ready-for-dev
|
||||||
3-5-logique-progression-deblocage-contact: ready-for-dev
|
3-5-logique-progression-deblocage-contact: ready-for-dev
|
||||||
|
|||||||
182
frontend/app/components/feature/NarratorBubble.vue
Normal file
182
frontend/app/components/feature/NarratorBubble.vue
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
message: string
|
||||||
|
visible: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: []
|
||||||
|
skip: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const progressionStore = useProgressionStore()
|
||||||
|
|
||||||
|
const messageRef = computed(() => props.message)
|
||||||
|
const { displayedText, isTyping, skip, start } = useTypewriter(messageRef, {
|
||||||
|
speed: 40,
|
||||||
|
})
|
||||||
|
|
||||||
|
const bugImages: Record<number, string> = {
|
||||||
|
1: '/images/bug/bug-stage-1.svg',
|
||||||
|
2: '/images/bug/bug-stage-2.svg',
|
||||||
|
3: '/images/bug/bug-stage-3.svg',
|
||||||
|
4: '/images/bug/bug-stage-4.svg',
|
||||||
|
5: '/images/bug/bug-stage-5.svg',
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentBugImage = computed(() => {
|
||||||
|
return bugImages[progressionStore.narratorStage] || bugImages[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(newVisible) => {
|
||||||
|
if (newVisible && props.message) {
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.message,
|
||||||
|
(newMessage) => {
|
||||||
|
if (newMessage && props.visible) {
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function handleInteraction() {
|
||||||
|
if (isTyping.value) {
|
||||||
|
skip()
|
||||||
|
emit('skip')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
|
if (e.code === 'Space' || e.code === 'Enter') {
|
||||||
|
e.preventDefault()
|
||||||
|
handleInteraction()
|
||||||
|
}
|
||||||
|
if (e.code === 'Escape') {
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Transition name="narrator-slide">
|
||||||
|
<div
|
||||||
|
v-if="visible"
|
||||||
|
class="narrator-bubble fixed bottom-4 left-4 right-4 md:left-auto md:right-8 md:max-w-md z-50"
|
||||||
|
role="status"
|
||||||
|
aria-live="polite"
|
||||||
|
tabindex="0"
|
||||||
|
@click="handleInteraction"
|
||||||
|
@keydown="handleKeydown"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex items-start gap-4 bg-sky-dark-50 rounded-xl p-4 shadow-xl border border-sky-dark-100"
|
||||||
|
>
|
||||||
|
<div class="shrink-0 w-16 h-16 md:w-20 md:h-20">
|
||||||
|
<img
|
||||||
|
:src="currentBugImage"
|
||||||
|
:alt="t('narrator.bugAlt', { stage: progressionStore.narratorStage })"
|
||||||
|
class="w-full h-full object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="font-narrative text-sky-text text-base md:text-lg leading-relaxed">
|
||||||
|
{{ displayedText }}
|
||||||
|
<span
|
||||||
|
v-if="isTyping"
|
||||||
|
class="inline-block w-0.5 h-5 bg-sky-accent animate-blink ml-0.5"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<span class="sr-only">{{ message }}</span>
|
||||||
|
|
||||||
|
<p v-if="isTyping" class="text-xs text-sky-text-muted mt-2 font-ui">
|
||||||
|
{{ t('narrator.clickToSkip') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="shrink-0 p-1 text-sky-text-muted hover:text-sky-text transition-colors"
|
||||||
|
:aria-label="t('common.close')"
|
||||||
|
@click.stop="emit('close')"
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.narrator-slide-enter-active,
|
||||||
|
.narrator-slide-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.narrator-slide-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.narrator-slide-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
0%,
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
51%,
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-blink {
|
||||||
|
animation: blink 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.narrator-bubble {
|
||||||
|
bottom: calc(var(--bottom-bar-height, 64px) + 1rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.narrator-slide-enter-active,
|
||||||
|
.narrator-slide-leave-active {
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.narrator-slide-enter-from,
|
||||||
|
.narrator-slide-leave-to {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-blink {
|
||||||
|
animation: none;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -175,6 +175,10 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"narrator": {
|
||||||
|
"clickToSkip": "Click or press Space to skip",
|
||||||
|
"bugAlt": "The Bug - Stage {stage}"
|
||||||
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
"projects": {
|
"projects": {
|
||||||
"title": "Projects",
|
"title": "Projects",
|
||||||
|
|||||||
@@ -175,6 +175,10 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"narrator": {
|
||||||
|
"clickToSkip": "Cliquez ou appuyez sur Espace pour passer",
|
||||||
|
"bugAlt": "Le Bug - Stade {stage}"
|
||||||
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
"projects": {
|
"projects": {
|
||||||
"title": "Projets",
|
"title": "Projets",
|
||||||
|
|||||||
19
frontend/public/images/bug/bug-stage-1.svg
Normal file
19
frontend/public/images/bug/bug-stage-1.svg
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
|
||||||
|
<!-- Stage 1: Silhouette sombre floue - mystérieuse forme indistincte -->
|
||||||
|
<defs>
|
||||||
|
<filter id="blur1" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="6"/>
|
||||||
|
</filter>
|
||||||
|
<radialGradient id="shadow1" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#1a1a2e"/>
|
||||||
|
<stop offset="70%" stop-color="#1a1a2e" stop-opacity="0.8"/>
|
||||||
|
<stop offset="100%" stop-color="#1a1a2e" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Forme mystérieuse floue -->
|
||||||
|
<ellipse cx="40" cy="42" rx="22" ry="20" fill="url(#shadow1)" filter="url(#blur1)"/>
|
||||||
|
|
||||||
|
<!-- Légère suggestion de présence -->
|
||||||
|
<ellipse cx="40" cy="40" rx="16" ry="14" fill="#16213e" opacity="0.7" filter="url(#blur1)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 845 B |
31
frontend/public/images/bug/bug-stage-2.svg
Normal file
31
frontend/public/images/bug/bug-stage-2.svg
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
|
||||||
|
<!-- Stage 2: Forme vague avec yeux - on commence à deviner une créature -->
|
||||||
|
<defs>
|
||||||
|
<filter id="blur2" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
|
||||||
|
</filter>
|
||||||
|
<radialGradient id="body2" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#2d3561"/>
|
||||||
|
<stop offset="100%" stop-color="#1a1a2e"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="glow2" cx="50%" cy="30%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#00d9ff" stop-opacity="0.8"/>
|
||||||
|
<stop offset="100%" stop-color="#00d9ff" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Corps vague -->
|
||||||
|
<ellipse cx="40" cy="42" rx="20" ry="18" fill="url(#body2)" filter="url(#blur2)"/>
|
||||||
|
|
||||||
|
<!-- Yeux brillants qui percent l'obscurité -->
|
||||||
|
<ellipse cx="32" cy="36" rx="5" ry="4" fill="url(#glow2)"/>
|
||||||
|
<ellipse cx="48" cy="36" rx="5" ry="4" fill="url(#glow2)"/>
|
||||||
|
|
||||||
|
<!-- Pupilles -->
|
||||||
|
<circle cx="33" cy="36" r="2" fill="#0a0a14"/>
|
||||||
|
<circle cx="49" cy="36" r="2" fill="#0a0a14"/>
|
||||||
|
|
||||||
|
<!-- Reflets -->
|
||||||
|
<circle cx="31" cy="35" r="1" fill="#ffffff" opacity="0.6"/>
|
||||||
|
<circle cx="47" cy="35" r="1" fill="#ffffff" opacity="0.6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
47
frontend/public/images/bug/bug-stage-3.svg
Normal file
47
frontend/public/images/bug/bug-stage-3.svg
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
|
||||||
|
<!-- Stage 3: Pattes visibles - créature avec membres qui apparaissent -->
|
||||||
|
<defs>
|
||||||
|
<filter id="blur3" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="1.5"/>
|
||||||
|
</filter>
|
||||||
|
<radialGradient id="body3" cx="50%" cy="40%" r="60%">
|
||||||
|
<stop offset="0%" stop-color="#3f4c7d"/>
|
||||||
|
<stop offset="100%" stop-color="#1a1a2e"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="glow3" cx="50%" cy="30%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#00d9ff"/>
|
||||||
|
<stop offset="100%" stop-color="#0099cc"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Pattes (partiellement visibles) -->
|
||||||
|
<g stroke="#2d3561" stroke-width="3" stroke-linecap="round" filter="url(#blur3)">
|
||||||
|
<!-- Pattes gauches -->
|
||||||
|
<path d="M24 35 L12 28"/>
|
||||||
|
<path d="M22 42 L8 42"/>
|
||||||
|
<path d="M24 49 L14 58"/>
|
||||||
|
|
||||||
|
<!-- Pattes droites -->
|
||||||
|
<path d="M56 35 L68 28"/>
|
||||||
|
<path d="M58 42 L72 42"/>
|
||||||
|
<path d="M56 49 L66 58"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Corps plus défini -->
|
||||||
|
<ellipse cx="40" cy="42" rx="18" ry="16" fill="url(#body3)"/>
|
||||||
|
|
||||||
|
<!-- Tête -->
|
||||||
|
<circle cx="40" cy="32" r="10" fill="#3f4c7d"/>
|
||||||
|
|
||||||
|
<!-- Yeux -->
|
||||||
|
<ellipse cx="35" cy="31" rx="4" ry="3.5" fill="url(#glow3)"/>
|
||||||
|
<ellipse cx="45" cy="31" rx="4" ry="3.5" fill="url(#glow3)"/>
|
||||||
|
|
||||||
|
<!-- Pupilles -->
|
||||||
|
<circle cx="36" cy="31" r="1.5" fill="#0a0a14"/>
|
||||||
|
<circle cx="46" cy="31" r="1.5" fill="#0a0a14"/>
|
||||||
|
|
||||||
|
<!-- Reflets -->
|
||||||
|
<circle cx="34" cy="30" r="1" fill="#ffffff" opacity="0.7"/>
|
||||||
|
<circle cx="44" cy="30" r="1" fill="#ffffff" opacity="0.7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
65
frontend/public/images/bug/bug-stage-4.svg
Normal file
65
frontend/public/images/bug/bug-stage-4.svg
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
|
||||||
|
<!-- Stage 4: Araignée reconnaissable - forme claire mais pas encore la mascotte finale -->
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="body4" cx="50%" cy="35%" r="60%">
|
||||||
|
<stop offset="0%" stop-color="#5a6aad"/>
|
||||||
|
<stop offset="100%" stop-color="#2d3561"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="glow4" cx="50%" cy="30%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#00ffff"/>
|
||||||
|
<stop offset="100%" stop-color="#00d9ff"/>
|
||||||
|
</radialGradient>
|
||||||
|
<filter id="glow4f" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="1"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Pattes bien définies -->
|
||||||
|
<g stroke="#3f4c7d" stroke-width="3" stroke-linecap="round">
|
||||||
|
<!-- Pattes gauches (4) -->
|
||||||
|
<path d="M26 30 L10 20 L6 12"/>
|
||||||
|
<path d="M24 38 L6 34 L2 28"/>
|
||||||
|
<path d="M24 46 L6 50 L2 56"/>
|
||||||
|
<path d="M26 52 L12 62 L8 70"/>
|
||||||
|
|
||||||
|
<!-- Pattes droites (4) -->
|
||||||
|
<path d="M54 30 L70 20 L74 12"/>
|
||||||
|
<path d="M56 38 L74 34 L78 28"/>
|
||||||
|
<path d="M56 46 L74 50 L78 56"/>
|
||||||
|
<path d="M54 52 L68 62 L72 70"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Abdomen -->
|
||||||
|
<ellipse cx="40" cy="48" rx="14" ry="12" fill="url(#body4)"/>
|
||||||
|
|
||||||
|
<!-- Céphalothorax -->
|
||||||
|
<ellipse cx="40" cy="34" rx="16" ry="14" fill="url(#body4)"/>
|
||||||
|
|
||||||
|
<!-- Motif sur l'abdomen -->
|
||||||
|
<path d="M40 38 L36 50 L40 56 L44 50 Z" fill="#4a5a9d" opacity="0.6"/>
|
||||||
|
|
||||||
|
<!-- Yeux (8 yeux d'araignée) -->
|
||||||
|
<g filter="url(#glow4f)">
|
||||||
|
<!-- Yeux principaux -->
|
||||||
|
<ellipse cx="35" cy="32" rx="4" ry="4" fill="url(#glow4)"/>
|
||||||
|
<ellipse cx="45" cy="32" rx="4" ry="4" fill="url(#glow4)"/>
|
||||||
|
|
||||||
|
<!-- Yeux secondaires -->
|
||||||
|
<circle cx="30" cy="28" r="2" fill="#00d9ff"/>
|
||||||
|
<circle cx="50" cy="28" r="2" fill="#00d9ff"/>
|
||||||
|
<circle cx="33" cy="38" r="1.5" fill="#00d9ff"/>
|
||||||
|
<circle cx="47" cy="38" r="1.5" fill="#00d9ff"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Pupilles principales -->
|
||||||
|
<circle cx="36" cy="32" r="2" fill="#0a0a14"/>
|
||||||
|
<circle cx="46" cy="32" r="2" fill="#0a0a14"/>
|
||||||
|
|
||||||
|
<!-- Reflets -->
|
||||||
|
<circle cx="34" cy="31" r="1.2" fill="#ffffff" opacity="0.8"/>
|
||||||
|
<circle cx="44" cy="31" r="1.2" fill="#ffffff" opacity="0.8"/>
|
||||||
|
|
||||||
|
<!-- Chélicères -->
|
||||||
|
<path d="M37 40 L35 44" stroke="#4a5a9d" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M43 40 L45 44" stroke="#4a5a9d" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
112
frontend/public/images/bug/bug-stage-5.svg
Normal file
112
frontend/public/images/bug/bug-stage-5.svg
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
|
||||||
|
<!-- Stage 5: Mascotte complète révélée - Le Bug dans toute sa splendeur -->
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="body5" cx="50%" cy="30%" r="70%">
|
||||||
|
<stop offset="0%" stop-color="#7b8cd4"/>
|
||||||
|
<stop offset="50%" stop-color="#5a6aad"/>
|
||||||
|
<stop offset="100%" stop-color="#3f4c7d"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="glow5" cx="50%" cy="30%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#00ffff"/>
|
||||||
|
<stop offset="60%" stop-color="#00d9ff"/>
|
||||||
|
<stop offset="100%" stop-color="#0099cc"/>
|
||||||
|
</radialGradient>
|
||||||
|
<filter id="glowFilter5" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="2"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="legGrad5" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#5a6aad"/>
|
||||||
|
<stop offset="100%" stop-color="#3f4c7d"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Halo de lumière derrière -->
|
||||||
|
<circle cx="40" cy="40" r="35" fill="#00d9ff" opacity="0.1" filter="url(#glowFilter5)"/>
|
||||||
|
|
||||||
|
<!-- Pattes avec détails -->
|
||||||
|
<g stroke="url(#legGrad5)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<!-- Pattes gauches -->
|
||||||
|
<path d="M26 28 L12 18 L8 8">
|
||||||
|
<animate attributeName="d" values="M26 28 L12 18 L8 8;M26 28 L11 17 L6 8;M26 28 L12 18 L8 8" dur="3s" repeatCount="indefinite"/>
|
||||||
|
</path>
|
||||||
|
<path d="M24 36 L8 32 L2 24"/>
|
||||||
|
<path d="M24 44 L8 48 L2 54"/>
|
||||||
|
<path d="M26 52 L14 62 L10 72">
|
||||||
|
<animate attributeName="d" values="M26 52 L14 62 L10 72;M26 52 L13 63 L8 72;M26 52 L14 62 L10 72" dur="3s" repeatCount="indefinite"/>
|
||||||
|
</path>
|
||||||
|
|
||||||
|
<!-- Pattes droites -->
|
||||||
|
<path d="M54 28 L68 18 L72 8">
|
||||||
|
<animate attributeName="d" values="M54 28 L68 18 L72 8;M54 28 L69 17 L74 8;M54 28 L68 18 L72 8" dur="3s" repeatCount="indefinite"/>
|
||||||
|
</path>
|
||||||
|
<path d="M56 36 L72 32 L78 24"/>
|
||||||
|
<path d="M56 44 L72 48 L78 54"/>
|
||||||
|
<path d="M54 52 L66 62 L70 72">
|
||||||
|
<animate attributeName="d" values="M54 52 L66 62 L70 72;M54 52 L67 63 L72 72;M54 52 L66 62 L70 72" dur="3s" repeatCount="indefinite"/>
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Articulations des pattes -->
|
||||||
|
<g fill="#4a5a9d">
|
||||||
|
<circle cx="12" cy="18" r="2"/>
|
||||||
|
<circle cx="8" cy="32" r="2"/>
|
||||||
|
<circle cx="8" cy="48" r="2"/>
|
||||||
|
<circle cx="14" cy="62" r="2"/>
|
||||||
|
<circle cx="68" cy="18" r="2"/>
|
||||||
|
<circle cx="72" cy="32" r="2"/>
|
||||||
|
<circle cx="72" cy="48" r="2"/>
|
||||||
|
<circle cx="66" cy="62" r="2"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Abdomen avec motif -->
|
||||||
|
<ellipse cx="40" cy="50" rx="14" ry="12" fill="url(#body5)"/>
|
||||||
|
<path d="M40 40 L34 52 L40 60 L46 52 Z" fill="#8b9ce0" opacity="0.5"/>
|
||||||
|
<ellipse cx="40" cy="52" rx="3" ry="4" fill="#9daef0" opacity="0.3"/>
|
||||||
|
|
||||||
|
<!-- Céphalothorax -->
|
||||||
|
<ellipse cx="40" cy="34" rx="16" ry="14" fill="url(#body5)"/>
|
||||||
|
|
||||||
|
<!-- Visage amical -->
|
||||||
|
<!-- Yeux principaux expressifs -->
|
||||||
|
<g filter="url(#glowFilter5)">
|
||||||
|
<ellipse cx="34" cy="32" rx="6" ry="6" fill="url(#glow5)"/>
|
||||||
|
<ellipse cx="46" cy="32" rx="6" ry="6" fill="url(#glow5)"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Bordure des yeux -->
|
||||||
|
<ellipse cx="34" cy="32" rx="6" ry="6" stroke="#00ffff" stroke-width="0.5" fill="none"/>
|
||||||
|
<ellipse cx="46" cy="32" rx="6" ry="6" stroke="#00ffff" stroke-width="0.5" fill="none"/>
|
||||||
|
|
||||||
|
<!-- Pupilles avec personnalité -->
|
||||||
|
<ellipse cx="35" cy="33" rx="2.5" ry="3" fill="#0a0a14"/>
|
||||||
|
<ellipse cx="47" cy="33" rx="2.5" ry="3" fill="#0a0a14"/>
|
||||||
|
|
||||||
|
<!-- Reflets vifs -->
|
||||||
|
<circle cx="33" cy="31" r="1.8" fill="#ffffff"/>
|
||||||
|
<circle cx="45" cy="31" r="1.8" fill="#ffffff"/>
|
||||||
|
<circle cx="36" cy="34" r="0.8" fill="#ffffff" opacity="0.6"/>
|
||||||
|
<circle cx="48" cy="34" r="0.8" fill="#ffffff" opacity="0.6"/>
|
||||||
|
|
||||||
|
<!-- Yeux secondaires (plus petits, amicaux) -->
|
||||||
|
<circle cx="28" cy="27" r="2.5" fill="#00d9ff"/>
|
||||||
|
<circle cx="52" cy="27" r="2.5" fill="#00d9ff"/>
|
||||||
|
<circle cx="28" cy="27" r="1" fill="#0a0a14"/>
|
||||||
|
<circle cx="52" cy="27" r="1" fill="#0a0a14"/>
|
||||||
|
|
||||||
|
<!-- Petits yeux inférieurs -->
|
||||||
|
<circle cx="32" cy="40" r="1.5" fill="#00d9ff" opacity="0.8"/>
|
||||||
|
<circle cx="48" cy="40" r="1.5" fill="#00d9ff" opacity="0.8"/>
|
||||||
|
|
||||||
|
<!-- Sourire amical -->
|
||||||
|
<path d="M36 42 Q40 46 44 42" stroke="#4a5a9d" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Chélicères stylisées (petites, pas effrayantes) -->
|
||||||
|
<path d="M37 44 L36 47" stroke="#5a6aad" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M43 44 L44 47" stroke="#5a6aad" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Petites antennes/pédipalpes amicales -->
|
||||||
|
<path d="M32 24 L28 18" stroke="#5a6aad" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M48 24 L52 18" stroke="#5a6aad" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<circle cx="28" cy="18" r="2" fill="#7b8cd4"/>
|
||||||
|
<circle cx="52" cy="18" r="2" fill="#7b8cd4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
Reference in New Issue
Block a user