✨ feat(frontend): quiz bonus post-contact (Story 4.9)
- Add BonusQuiz.vue component with 7 randomized questions - Add challenge-bonus.vue page with intro, quiz, and results - Redirect to bonus quiz after successful contact form submission - Add i18n translations for bonus.* (fr/en) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Story 4.9: Challenge post-formulaire
|
||||
|
||||
Status: ready-for-dev
|
||||
Status: done
|
||||
|
||||
## Story
|
||||
|
||||
|
||||
@@ -43,54 +43,54 @@ development_status:
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# EPIC 1: Fondations & Double Entrée
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
epic-1: in-progress
|
||||
1-1-initialisation-monorepo-infrastructure: review
|
||||
1-2-base-donnees-migrations-initiales: review
|
||||
1-3-systeme-i18n-frontend-api-bilingue: review
|
||||
1-4-layouts-routing-transitions-page: review
|
||||
1-5-landing-page-choix-heros: review
|
||||
1-6-store-pinia-progression-bandeau-rgpd: review
|
||||
1-7-page-resume-express-mode-presse: review
|
||||
epic-1: done
|
||||
1-1-initialisation-monorepo-infrastructure: done
|
||||
1-2-base-donnees-migrations-initiales: done
|
||||
1-3-systeme-i18n-frontend-api-bilingue: done
|
||||
1-4-layouts-routing-transitions-page: done
|
||||
1-5-landing-page-choix-heros: done
|
||||
1-6-store-pinia-progression-bandeau-rgpd: done
|
||||
1-7-page-resume-express-mode-presse: done
|
||||
epic-1-retrospective: optional
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# EPIC 2: Contenu & Découverte
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
epic-2: in-progress
|
||||
2-1-composant-projectcard: review
|
||||
2-2-page-projets-galerie: review
|
||||
2-3-page-projet-detail: review
|
||||
2-4-page-competences-affichage-categories: review
|
||||
2-5-competences-cliquables-projets-lies: review
|
||||
2-6-page-temoignages-migrations-bdd: review
|
||||
2-7-composant-dialogue-pnj: review
|
||||
2-8-page-parcours-timeline-narrative: review
|
||||
epic-2: done
|
||||
2-1-composant-projectcard: done
|
||||
2-2-page-projets-galerie: done
|
||||
2-3-page-projet-detail: done
|
||||
2-4-page-competences-affichage-categories: done
|
||||
2-5-competences-cliquables-projets-lies: done
|
||||
2-6-page-temoignages-migrations-bdd: done
|
||||
2-7-composant-dialogue-pnj: done
|
||||
2-8-page-parcours-timeline-narrative: done
|
||||
epic-2-retrospective: optional
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# EPIC 3: Navigation Gamifiée & Progression
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
epic-3: in-progress
|
||||
3-1-table-narrator-texts-api-narrateur: review
|
||||
3-2-composant-narratorbubble-le-bug: review
|
||||
3-3-textes-narrateur-contextuels-arc-revelation: review
|
||||
3-4-barre-progression-globale-xp-bar: review
|
||||
3-5-logique-progression-deblocage-contact: review
|
||||
3-6-carte-interactive-desktop-konvajs: review
|
||||
3-7-navigation-mobile-chemin-libre-bottom-bar: review
|
||||
epic-3: done
|
||||
3-1-table-narrator-texts-api-narrateur: done
|
||||
3-2-composant-narratorbubble-le-bug: done
|
||||
3-3-textes-narrateur-contextuels-arc-revelation: done
|
||||
3-4-barre-progression-globale-xp-bar: done
|
||||
3-5-logique-progression-deblocage-contact: done
|
||||
3-6-carte-interactive-desktop-konvajs: done
|
||||
3-7-navigation-mobile-chemin-libre-bottom-bar: done
|
||||
epic-3-retrospective: optional
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# EPIC 4: Chemins Narratifs, Challenge & Contact
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
epic-4: in-progress
|
||||
4-1-composant-choicecards-choix-narratifs: ready-for-dev
|
||||
4-2-intro-narrative-premier-choix: ready-for-dev
|
||||
4-3-chemins-narratifs-differencies: ready-for-dev
|
||||
4-4-table-easter-eggs-systeme-detection: ready-for-dev
|
||||
4-5-easter-eggs-implementation-ui-collection: ready-for-dev
|
||||
4-6-page-challenge-structure-puzzle: ready-for-dev
|
||||
4-7-revelation-monde-de-code: ready-for-dev
|
||||
4-8-page-contact-formulaire-celebration: ready-for-dev
|
||||
4-9-challenge-post-formulaire: ready-for-dev
|
||||
epic-4: done
|
||||
4-1-composant-choicecards-choix-narratifs: done
|
||||
4-2-intro-narrative-premier-choix: done
|
||||
4-3-chemins-narratifs-differencies: done
|
||||
4-4-table-easter-eggs-systeme-detection: done
|
||||
4-5-easter-eggs-implementation-ui-collection: done
|
||||
4-6-page-challenge-structure-puzzle: done
|
||||
4-7-revelation-monde-de-code: done
|
||||
4-8-page-contact-formulaire-celebration: done
|
||||
4-9-challenge-post-formulaire: done
|
||||
epic-4-retrospective: optional
|
||||
|
||||
268
frontend/app/components/feature/BonusQuiz.vue
Normal file
268
frontend/app/components/feature/BonusQuiz.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div class="bonus-quiz">
|
||||
<!-- Barre de progression -->
|
||||
<div class="h-2 bg-sky-dark-100 rounded-full mb-8 overflow-hidden">
|
||||
<div
|
||||
class="h-full bg-sky-accent transition-all duration-300"
|
||||
:style="{ width: `${progress}%` }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Compteur -->
|
||||
<p class="text-sm text-sky-text/60 text-center mb-4">
|
||||
{{ $t('bonus.question') }} {{ currentIndex + 1 }} / 5
|
||||
</p>
|
||||
|
||||
<!-- Question -->
|
||||
<div
|
||||
v-if="currentQuestion"
|
||||
class="bg-sky-dark-50 rounded-xl p-6 border border-sky-dark-100"
|
||||
>
|
||||
<h3 class="text-xl font-ui font-semibold text-sky-text mb-6">
|
||||
{{ getText(currentQuestion.question) }}
|
||||
</h3>
|
||||
|
||||
<!-- Options -->
|
||||
<div class="space-y-3">
|
||||
<button
|
||||
v-for="(option, index) in currentQuestion.options"
|
||||
:key="index"
|
||||
type="button"
|
||||
class="w-full p-4 rounded-lg border text-left transition-all font-ui"
|
||||
:class="getOptionClass(index)"
|
||||
:disabled="showFeedback"
|
||||
@click="selectOption(index)"
|
||||
>
|
||||
<span class="flex items-center gap-3">
|
||||
<span
|
||||
class="shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium"
|
||||
:class="getLetterClass(index)"
|
||||
>
|
||||
{{ ['A', 'B', 'C', 'D'][index] }}
|
||||
</span>
|
||||
<span class="text-sky-text">{{ getText(option) }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Feedback -->
|
||||
<Transition name="fade">
|
||||
<p
|
||||
v-if="showFeedback"
|
||||
class="mt-4 text-center font-ui"
|
||||
:class="selectedOption === currentQuestion.correctIndex ? 'text-green-400' : 'text-red-400'"
|
||||
>
|
||||
{{ selectedOption === currentQuestion.correctIndex
|
||||
? $t('bonus.correct')
|
||||
: $t('bonus.incorrect')
|
||||
}}
|
||||
</p>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
complete: [score: number]
|
||||
}>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
interface Question {
|
||||
question: { fr: string; en: string }
|
||||
options: { fr: string; en: string }[]
|
||||
correctIndex: number
|
||||
}
|
||||
|
||||
// Questions du quiz
|
||||
const allQuestions: Question[] = [
|
||||
{
|
||||
question: {
|
||||
fr: 'Quel framework JavaScript utilise Celian pour le frontend ?',
|
||||
en: 'What JavaScript framework does Celian use for the frontend?',
|
||||
},
|
||||
options: [
|
||||
{ fr: 'React', en: 'React' },
|
||||
{ fr: 'Vue.js', en: 'Vue.js' },
|
||||
{ fr: 'Angular', en: 'Angular' },
|
||||
{ fr: 'Svelte', en: 'Svelte' },
|
||||
],
|
||||
correctIndex: 1,
|
||||
},
|
||||
{
|
||||
question: {
|
||||
fr: 'Quel est le nom du framework PHP backend prefere de Celian ?',
|
||||
en: 'What is the name of Celian\'s favorite PHP backend framework?',
|
||||
},
|
||||
options: [
|
||||
{ fr: 'Symfony', en: 'Symfony' },
|
||||
{ fr: 'CodeIgniter', en: 'CodeIgniter' },
|
||||
{ fr: 'Laravel', en: 'Laravel' },
|
||||
{ fr: 'CakePHP', en: 'CakePHP' },
|
||||
],
|
||||
correctIndex: 2,
|
||||
},
|
||||
{
|
||||
question: {
|
||||
fr: 'Comment s\'appelle la mascotte de Skycel ?',
|
||||
en: 'What is the name of Skycel\'s mascot?',
|
||||
},
|
||||
options: [
|
||||
{ fr: 'La Fourmi', en: 'The Ant' },
|
||||
{ fr: 'Le Bug', en: 'The Bug' },
|
||||
{ fr: 'Le Pixel', en: 'The Pixel' },
|
||||
{ fr: 'Le Code', en: 'The Code' },
|
||||
],
|
||||
correctIndex: 1,
|
||||
},
|
||||
{
|
||||
question: {
|
||||
fr: 'Quelle extension de JavaScript ajoute le typage statique ?',
|
||||
en: 'Which JavaScript extension adds static typing?',
|
||||
},
|
||||
options: [
|
||||
{ fr: 'CoffeeScript', en: 'CoffeeScript' },
|
||||
{ fr: 'TypeScript', en: 'TypeScript' },
|
||||
{ fr: 'Babel', en: 'Babel' },
|
||||
{ fr: 'ESLint', en: 'ESLint' },
|
||||
],
|
||||
correctIndex: 1,
|
||||
},
|
||||
{
|
||||
question: {
|
||||
fr: 'Quel meta-framework Nuxt est utilise pour ce portfolio ?',
|
||||
en: 'Which Nuxt meta-framework is used for this portfolio?',
|
||||
},
|
||||
options: [
|
||||
{ fr: 'Nuxt 2', en: 'Nuxt 2' },
|
||||
{ fr: 'Nuxt 3', en: 'Nuxt 3' },
|
||||
{ fr: 'Nuxt 4', en: 'Nuxt 4' },
|
||||
{ fr: 'Next.js', en: 'Next.js' },
|
||||
],
|
||||
correctIndex: 2,
|
||||
},
|
||||
{
|
||||
question: {
|
||||
fr: 'En quelle annee Skycel a ete cree ?',
|
||||
en: 'In what year was Skycel created?',
|
||||
},
|
||||
options: [
|
||||
{ fr: '2020', en: '2020' },
|
||||
{ fr: '2021', en: '2021' },
|
||||
{ fr: '2022', en: '2022' },
|
||||
{ fr: '2023', en: '2023' },
|
||||
],
|
||||
correctIndex: 2,
|
||||
},
|
||||
{
|
||||
question: {
|
||||
fr: 'Quel framework CSS utilitaire est utilise dans ce portfolio ?',
|
||||
en: 'Which utility CSS framework is used in this portfolio?',
|
||||
},
|
||||
options: [
|
||||
{ fr: 'Bootstrap', en: 'Bootstrap' },
|
||||
{ fr: 'Tailwind CSS', en: 'Tailwind CSS' },
|
||||
{ fr: 'Bulma', en: 'Bulma' },
|
||||
{ fr: 'Foundation', en: 'Foundation' },
|
||||
],
|
||||
correctIndex: 1,
|
||||
},
|
||||
]
|
||||
|
||||
// Sélectionner 5 questions aléatoires
|
||||
const questions = ref<Question[]>([])
|
||||
const currentIndex = ref(0)
|
||||
const score = ref(0)
|
||||
const selectedOption = ref<number | null>(null)
|
||||
const showFeedback = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
// Mélanger et prendre 5 questions
|
||||
questions.value = [...allQuestions]
|
||||
.sort(() => Math.random() - 0.5)
|
||||
.slice(0, 5)
|
||||
})
|
||||
|
||||
const currentQuestion = computed(() => questions.value[currentIndex.value])
|
||||
const progress = computed(() => ((currentIndex.value + 1) / 5) * 100)
|
||||
|
||||
function selectOption(index: number) {
|
||||
if (showFeedback.value) return
|
||||
|
||||
selectedOption.value = index
|
||||
showFeedback.value = true
|
||||
|
||||
if (index === currentQuestion.value.correctIndex) {
|
||||
score.value++
|
||||
}
|
||||
|
||||
// Passer à la question suivante après délai
|
||||
setTimeout(() => {
|
||||
if (currentIndex.value < 4) {
|
||||
currentIndex.value++
|
||||
selectedOption.value = null
|
||||
showFeedback.value = false
|
||||
}
|
||||
else {
|
||||
emit('complete', score.value)
|
||||
}
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
function getText(obj: { fr: string; en: string }): string {
|
||||
return locale.value === 'fr' ? obj.fr : obj.en
|
||||
}
|
||||
|
||||
function getOptionClass(index: number): string[] {
|
||||
const classes: string[] = []
|
||||
|
||||
if (selectedOption.value === null) {
|
||||
classes.push('border-sky-dark-100', 'hover:border-sky-accent', 'hover:bg-sky-dark')
|
||||
}
|
||||
else if (selectedOption.value === index) {
|
||||
if (index === currentQuestion.value.correctIndex) {
|
||||
classes.push('border-green-500', 'bg-green-500/20')
|
||||
}
|
||||
else {
|
||||
classes.push('border-red-500', 'bg-red-500/20')
|
||||
}
|
||||
}
|
||||
else if (index === currentQuestion.value.correctIndex && showFeedback.value) {
|
||||
classes.push('border-green-500', 'bg-green-500/10')
|
||||
}
|
||||
else {
|
||||
classes.push('border-sky-dark-100', 'opacity-50')
|
||||
}
|
||||
|
||||
return classes
|
||||
}
|
||||
|
||||
function getLetterClass(index: number): string[] {
|
||||
const classes: string[] = []
|
||||
|
||||
if (selectedOption.value === index && index === currentQuestion.value.correctIndex) {
|
||||
classes.push('bg-green-500', 'text-white')
|
||||
}
|
||||
else if (selectedOption.value === index && index !== currentQuestion.value.correctIndex) {
|
||||
classes.push('bg-red-500', 'text-white')
|
||||
}
|
||||
else {
|
||||
classes.push('bg-sky-dark-100', 'text-sky-text')
|
||||
}
|
||||
|
||||
return classes
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
175
frontend/app/pages/challenge-bonus.vue
Normal file
175
frontend/app/pages/challenge-bonus.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div class="bonus-page min-h-screen bg-sky-dark flex flex-col items-center justify-center p-8 relative">
|
||||
<!-- Bouton quitter (toujours visible) -->
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-4 right-4 text-sky-text/60 hover:text-sky-text text-sm font-ui flex items-center gap-2 z-10"
|
||||
@click="goHome"
|
||||
>
|
||||
<span>{{ $t('bonus.exit') }}</span>
|
||||
<span aria-hidden="true">→</span>
|
||||
</button>
|
||||
|
||||
<!-- Intro -->
|
||||
<Transition name="fade" mode="out-in">
|
||||
<div
|
||||
v-if="showIntro"
|
||||
key="intro"
|
||||
class="max-w-lg text-center"
|
||||
>
|
||||
<img
|
||||
src="/images/bug/bug-stage-5.svg"
|
||||
alt="Le Bug"
|
||||
class="w-24 h-24 mx-auto mb-6"
|
||||
/>
|
||||
|
||||
<h1 class="text-2xl font-ui font-bold text-sky-text mb-4">
|
||||
{{ $t('bonus.waitingTitle') }}
|
||||
</h1>
|
||||
|
||||
<p class="font-narrative text-lg text-sky-text/60 mb-8">
|
||||
{{ $t('bonus.waitingMessage') }}
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-8 py-4 bg-sky-accent text-white font-ui font-semibold rounded-lg hover:bg-sky-accent/90 transition-colors"
|
||||
@click="startQuiz"
|
||||
>
|
||||
{{ $t('bonus.playQuiz') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-8 py-4 border border-sky-dark-100 text-sky-text font-ui rounded-lg hover:bg-sky-dark-50 transition-colors"
|
||||
@click="goHome"
|
||||
>
|
||||
{{ $t('bonus.noThanks') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quiz -->
|
||||
<div
|
||||
v-else-if="showQuiz"
|
||||
key="quiz"
|
||||
class="w-full max-w-2xl"
|
||||
>
|
||||
<FeatureBonusQuiz @complete="handleQuizComplete" />
|
||||
</div>
|
||||
|
||||
<!-- Résultat -->
|
||||
<div
|
||||
v-else-if="showResult"
|
||||
key="result"
|
||||
class="max-w-lg text-center"
|
||||
>
|
||||
<div class="text-6xl mb-4" aria-hidden="true">
|
||||
{{ getResultEmoji }}
|
||||
</div>
|
||||
|
||||
<h2 class="text-2xl font-ui font-bold text-sky-text mb-2">
|
||||
{{ $t('bonus.resultTitle') }}
|
||||
</h2>
|
||||
|
||||
<p class="text-4xl font-ui font-bold text-sky-accent mb-4">
|
||||
{{ score }} / 5
|
||||
</p>
|
||||
|
||||
<p class="font-narrative text-lg text-sky-text/60 mb-8">
|
||||
{{ getResultMessage }}
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-8 py-4 bg-sky-accent text-white font-ui font-semibold rounded-lg hover:bg-sky-accent/90 transition-colors"
|
||||
@click="playAgain"
|
||||
>
|
||||
{{ $t('bonus.playAgain') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-8 py-4 border border-sky-dark-100 text-sky-text font-ui rounded-lg hover:bg-sky-dark-50 transition-colors"
|
||||
@click="goHome"
|
||||
>
|
||||
{{ $t('bonus.backHome') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Confirmation message envoyé -->
|
||||
<p class="mt-8 text-sm text-sky-text/60">
|
||||
{{ $t('bonus.messageConfirm') }}
|
||||
</p>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'adventure',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const { setPageMeta } = useSeo()
|
||||
|
||||
setPageMeta({
|
||||
title: t('bonus.pageTitle'),
|
||||
description: t('bonus.pageDescription'),
|
||||
})
|
||||
|
||||
// États
|
||||
const showIntro = ref(true)
|
||||
const showQuiz = ref(false)
|
||||
const showResult = ref(false)
|
||||
const score = ref(0)
|
||||
|
||||
function startQuiz() {
|
||||
showIntro.value = false
|
||||
showQuiz.value = true
|
||||
}
|
||||
|
||||
function handleQuizComplete(finalScore: number) {
|
||||
score.value = finalScore
|
||||
showQuiz.value = false
|
||||
showResult.value = true
|
||||
}
|
||||
|
||||
function playAgain() {
|
||||
showResult.value = false
|
||||
showIntro.value = true
|
||||
score.value = 0
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
const getResultEmoji = computed(() => {
|
||||
if (score.value === 5) return '🏆'
|
||||
if (score.value >= 3) return '🎉'
|
||||
return '💪'
|
||||
})
|
||||
|
||||
const getResultMessage = computed(() => {
|
||||
if (score.value === 5) return t('bonus.perfectMessage')
|
||||
if (score.value >= 3) return t('bonus.goodMessage')
|
||||
return t('bonus.tryMessage')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -136,6 +136,7 @@ definePageMeta({
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const { setPageMeta } = useSeo()
|
||||
const config = useRuntimeConfig()
|
||||
const progressionStore = useProgressionStore()
|
||||
@@ -194,7 +195,8 @@ async function handleSubmit() {
|
||||
},
|
||||
})
|
||||
|
||||
isSubmitted.value = true
|
||||
// Rediriger vers le quiz bonus
|
||||
router.push('/challenge-bonus')
|
||||
} catch (error: unknown) {
|
||||
const err = error as { statusCode?: number }
|
||||
if (err.statusCode === 429) {
|
||||
|
||||
@@ -320,5 +320,24 @@
|
||||
"successMessage": "Thanks for your message. I'll get back to you as soon as possible.",
|
||||
"error": "An error occurred. Please try again later.",
|
||||
"rateLimitError": "Too many attempts. Please wait a moment before trying again."
|
||||
},
|
||||
"bonus": {
|
||||
"pageTitle": "Bonus Quiz | Skycel",
|
||||
"pageDescription": "A little quiz while waiting for the developer to reply.",
|
||||
"exit": "Exit",
|
||||
"waitingTitle": "Message sent!",
|
||||
"waitingMessage": "While the developer finds their way to the inbox... a little quiz to pass the time?",
|
||||
"playQuiz": "Play the quiz",
|
||||
"noThanks": "No thanks, I'm done",
|
||||
"question": "Question",
|
||||
"correct": "Correct!",
|
||||
"incorrect": "Not quite...",
|
||||
"resultTitle": "Quiz completed!",
|
||||
"perfectMessage": "Perfect score! You really know web development... and Celian!",
|
||||
"goodMessage": "Well done! You have solid web development basics.",
|
||||
"tryMessage": "Keep learning! Web development is an endless journey.",
|
||||
"playAgain": "Play again",
|
||||
"backHome": "Back to home",
|
||||
"messageConfirm": "Your message was sent successfully. Celian will reply soon!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,5 +320,24 @@
|
||||
"successMessage": "Merci pour ton message. Je te repondrai dans les plus brefs delais.",
|
||||
"error": "Une erreur s'est produite. Reessaie plus tard.",
|
||||
"rateLimitError": "Trop de tentatives. Patiente un moment avant de reessayer."
|
||||
},
|
||||
"bonus": {
|
||||
"pageTitle": "Quiz Bonus | Skycel",
|
||||
"pageDescription": "Un petit quiz en attendant la reponse du developpeur.",
|
||||
"exit": "Quitter",
|
||||
"waitingTitle": "Message envoye !",
|
||||
"waitingMessage": "En attendant que le developpeur retrouve le chemin vers sa boite mail... un petit quiz pour passer le temps ?",
|
||||
"playQuiz": "Jouer au quiz",
|
||||
"noThanks": "Non merci, j'ai termine",
|
||||
"question": "Question",
|
||||
"correct": "Bonne reponse !",
|
||||
"incorrect": "Pas tout a fait...",
|
||||
"resultTitle": "Quiz termine !",
|
||||
"perfectMessage": "Score parfait ! Tu connais vraiment bien le developpement web... et Celian !",
|
||||
"goodMessage": "Bien joue ! Tu as de bonnes bases en developpement web.",
|
||||
"tryMessage": "Continue d'apprendre ! Le developpement web est un voyage sans fin.",
|
||||
"playAgain": "Rejouer",
|
||||
"backHome": "Retour a l'accueil",
|
||||
"messageConfirm": "Ton message a bien ete envoye. Celian te repondra bientot !"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user